mips处理器linux内核pci初始化和设备枚举详解

Linux启动过程中pci总线初始化主要包括2部分,pci控制器的注册和pci设备的枚举,pci总线和其他总线一个很重要的区别就是pci总线的枚举,在启动过程中遍历pci总线树上所有可能的dev func,记录下所有存在的设备的vendor id  设备名等,这个是做为后面pci设备驱动初始化中注册pci设备驱动需要匹配的重要依据,类似于platform驱动。

  先说pci控制器注册,这个与具体开发板相关,以龙芯1A开发板2.6.33内核为例,在arch/mips/loongson/sb2f/pci.c中部分代码如下:

static struct resource loongson_pci_mem_resource = {
    .name   = "pci memory space",
    .start  = 0x14000000UL,
    .end    = 0x17ffffffUL,
    .flags  = IORESOURCE_MEM,
};

static struct resource loongson_pci_io_resource = {
    .name   = "pci io space",
    .start  = 0x00004000UL,
    .end    = IO_SPACE_LIMIT,
    .flags  = IORESOURCE_IO,
};

static struct pci_controller  loongson_pci_controller = {
    .pci_ops        = &sb2f_pci_pci_ops,
    .io_resource    = &loongson_pci_io_resource,
    .mem_resource   = &loongson_pci_mem_resource,
    .mem_offset     = 0x00000000UL,
    .io_offset      = 0x00000000UL,
};

static void __init setup_pcimap(void)
{
    /*
     * local to PCI mapping for CPU accessing PCI space
     * CPU address space [256M,448M] is window for accessing pci space
     * we set pcimap_lo[0,1,2] to map it to pci space[0M,64M], [320M,448M]
     *
     * pcimap: PCI_MAP2  PCI_Mem_Lo2 PCI_Mem_Lo1 PCI_Mem_Lo0
     *       [<2G]   [384M,448M] [320M,384M] [0M,64M]
     */
    SB2F_PCIMAP = 0x46140;
}

static int __init pcibios_init(void)
{
    setup_pcimap();

    //loongson_pci_controller.io_map_base = mips_io_port_base;

    if(!disablepci)
    {
        register_pci_conroller(&loongson_pci_controller);
    }

    return 0;
}
在pcibios_init函数中setup_pcimap中设置pci总线的3个mem space空间的高6位, register_pci_conroller函数是对pci控制器提供的mem和io资源做一下检查 然后将pci控制器添加到内核的pci控制器链表中。

这就完成了对特定pci控制器的初始化。特别要注意控制器结构体中的pci_ops成员,这个成员提供的读写函数就是对pci设备配置寄存器的读写。


接下来来看pci总线的枚举,这是非常重要的一部分。

在arch/mips/pci/pci.c中

static int __init pcibios_init(void)
{
    struct pci_controller *hose;
    /* Scan all of the recorded PCI controllers.  */
    for (hose = hose_head; hose; hose = hose->next)
    {   
        pcibios_scanbus(hose);
    }   

    pci_fixup_irqs(pci_common_swizzle, pcibios_map_irq);

    pci_initialized = 1;

    return 0;
}


在这个pcibios_init函数中,遍历内核pci控制器链表每一个成员,这里我们只有一个控制器,只有一个成员。然后调用pcibus_scanbus来遍历总线,扫描设备。

-------》》》》pcibus_scanbus  参数:hose  pci控制器结构体

static void __devinit pcibios_scanbus(struct pci_controller *hose)
{
    static int next_busno;
    static int need_domain_info;
    struct pci_bus *bus;

    if (!hose->iommu)
        PCI_DMA_BUS_IS_PHYS = 1;

    if (hose->get_busno && pci_probe_only)
        next_busno = (*hose->get_busno)(); 

    bus = pci_scan_bus(next_busno, hose->pci_ops, hose);
    hose->bus = bus;

    need_domain_info = need_domain_info || hose->index;
    hose->need_domain_info = need_domain_info;
    if (bus) {
        next_busno = bus->subordinate + 1;
        /* Don't allow 8-bit bus number overflow inside the hose -
           reserve some space for bridges. */
        if (next_busno > 224) {
            next_busno = 0;
            need_domain_info = 1;
        }

        if (!pci_probe_only) {
            pci_bus_size_bridges(bus);
            pci_bus_assign_resources(bus);
            pci_enable_bridges(bus);
        }
    }
}
从前面pci控制器的注册可以知道get_busno为NULL,并且pci_probe_only为0,因此next_busno为0。调用pci_scan_bus

--------》》》》pci_scan_bus 参数:next_busno=0 hose->pci_ops hose

static inline struct pci_bus * __devinit pci_scan_bus(int bus, struct pci_ops *ops,
                       void *sysdata)
{       
    struct pci_bus *root_bus;
    root_bus = pci_scan_bus_parented(NULL, bus, ops, sysdata);
    if (root_bus)
        pci_bus_add_devices(root_bus);
    return root_bus; 
}   
这个函数实现对pci根总线的初始化

------》》》》pci_scan_bus_parented 参数:NULL 0 hose->ops hose

struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent,
        int bus, struct pci_ops *ops, void *sysdata)
{       
    struct pci_bus *b;
    
    b = pci_create_bus(parent, bus, ops, sysdata);
    if (b)
        b->subordinate = pci_scan_child_bus(b);
    return b;
}
调用pci_create_bus检查在这个domain中bus号是否存在,如果存在分配初始化一个pci总线结构体,注册设备。

完成pci_create_bus后就有了根总线结构体,调用pci_scan_child_bus。

-------》》》》pci_scan_child_bus 参数 : b 总线结构体

unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)
{
    unsigned int devfn, pass, max = bus->secondary;
    struct pci_dev *dev;

    dev_dbg(&bus->dev, "scanning bus\n");

    /* Go find them, Rover! */
    for (devfn = 0; devfn < 0x100; devfn += 8)
        pci_scan_slot(bus, devfn);

    /* Reserve buses for SR-IOV capability. */
    max += pci_iov_bus_range(bus);

    /*
     * After performing arch-dependent fixup of the bus, look behind
     * all PCI-to-PCI bridges on this bus.
     */
    if (!bus->is_added) {
        dev_dbg(&bus->dev, "fixups for bus\n");
        pcibios_fixup_bus(bus);
        if (pci_is_root_bus(bus))
            bus->is_added = 1;
    }

    for (pass=0; pass < 2; pass++)
        list_for_each_entry(dev, &bus->devices, bus_list) {
            if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
                dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
                max = pci_scan_bridge(bus, dev, max, pass);
        }

    /*
     * We've scanned the bus and so we know all about what's on
     * the other side of any bridges that may be on this bus plus
     * any devices.
     *
     * Return how far we've got finding sub-buses.
     */
    dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
    return max;
}
循环devfn实现了对256个功能(每个pci总线最多支持32个设备,每个设备最多8个功能)遍历。每8个功能(1个设备)为一次,调用pci_scan_slot。

-------》》》》pci_scan_slot 参数:bus 总线号  devfn 每个设备(插槽)的第一设备功能号

int pci_scan_slot(struct pci_bus *bus, int devfn)
{
    int fn, nr = 0;
    struct pci_dev *dev;

    dev = pci_scan_single_device(bus, devfn);
    if (dev && !dev->is_added)  /* new device? */
        nr++;
    
    if (dev && dev->multifunction) {
        for (fn = 1; fn < 8; fn++) {
            dev = pci_scan_single_device(bus, devfn + fn);
            if (dev) {
                if (!dev->is_added)
                    nr++;
                dev->multifunction = 1;
            }
        }
    }
    
    /* only one slot has pcie device */
    if (bus->self && nr)
        pcie_aspm_init_link_state(bus->self);

    return nr;
}   
首先以devfn(某设备的func0)来扫描设备,如果扫描到的设备是多功能,则再扫描剩余的7个功能卡,看有无设备。

-------》》》》pci_scan_single_device 参数:bus结构体 devfn

struct pci_dev *__ref pci_scan_single_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    
    dev = pci_get_slot(bus, devfn);
    if (dev) {
        pci_dev_put(dev);
        return dev;
    }

    dev = pci_scan_device(bus, devfn);
    if (!dev)
        return NULL;

    pci_device_add(dev, bus);

    return dev;
}   
pci_get_slot是对bus总线上的已有设备以devfn进行匹配,如果已经有这个设备了,就不需要在扫描。如果没有调用pci_scan_device扫描bus总线上的devfn号设备。

-------》》》》pci_scan_device

static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    u32 l;
    int delay = 1;

    if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
        return NULL;
    /* some broken boards return 0 or ~0 if a slot is empty: */
    if (l == 0xffffffff || l == 0x00000000 ||
        l == 0x0000ffff || l == 0xffff0000)
        return NULL;

    /* Configuration request Retry Status */
    while (l == 0xffff0001) {
        msleep(delay);
        delay *= 2;
        if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
            return NULL;
        /* Card hasn't responded in 60 seconds?  Must be stuck. */
        if (delay > 60 * 1000) {
            printk(KERN_WARNING "pci %04x:%02x:%02x.%d: not "
                    "responding\n", pci_domain_nr(bus),
                    bus->number, PCI_SLOT(devfn),
                    PCI_FUNC(devfn));
            return NULL;
        }
    }

    dev = alloc_pci_dev();
    if (!dev)
        return NULL;

    dev->bus = bus;
    dev->devfn = devfn;
    dev->vendor = l & 0xffff;
    dev->device = (l >> 16) & 0xffff;

    if (pci_setup_device(dev)) {
        kfree(dev);
        return NULL;
    }

    return dev;
}
调用函数pci_bus_read_config_dword读取指定devfn号的vendor id,pci_bus_read_config_dword的实现在access.c中,最终是调用了bus->ops->read,bus->ops在pci_bus_create中北赋值了hose的ops,这也就回到了文章开头说的重要的pci_ops成员。read write的实现与具体的平台相关,这里不在贴代码,这个已经是最低层的实现,到这里pci总线从控制器注册,根总线的分配注册,第一个设备的扫描已经从上到下完成,如果有设备,就会成功读出vendor id,这个也是pci枚举最重要的部分,如果pci总线出了问题,大部分应该出在这里,特别是底层的读写。

从读vendor id返回,然后层层往上,都是一些逻辑的东西,这些逻辑都是内核开发人员试验了无数次,问题一般不会处在这里,但是学习这些逻辑的东西能够让我们对内核pci总线的工作有更深层次的了解。

读出vendor id之后做一些判断以及重试,如果vendor id没有问题,则调用alloc_pci_dev来分配pci_dev结构体,初始化成员bus devfn 以及vendor device。接着调用pci_setup_device

--------》》》》pci_setup_device 参数:pci_dev

int pci_setup_device(struct pci_dev *dev)
{
    u32 class;
    u8 hdr_type;
    struct pci_slot *slot;
    int pos = 0;

    //读取配置空间header寄存器
    if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type))
        return -EIO;

    dev->sysdata = dev->bus->sysdata;
    dev->dev.parent = dev->bus->bridge;
    dev->dev.bus = &pci_bus_type;

    //设置header type

    dev->hdr_type = hdr_type & 0x7f;

    //是否为多功能设备

    dev->multifunction = !!(hdr_type & 0x80);
    dev->error_state = pci_channel_io_normal;
    set_pcie_port_type(dev);
    set_pci_aer_firmware_first(dev);

    list_for_each_entry(slot, &dev->bus->slots, list)
        if (PCI_SLOT(dev->devfn) == slot->number)
            dev->slot = slot;

    dev->dma_mask = 0xffffffff;

    dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
             dev->bus->number, PCI_SLOT(dev->devfn),
             PCI_FUNC(dev->devfn));

    //读取版本寄存器,设置版本和类
    pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
    dev->revision = class & 0xff;

    class >>= 8;                    /* upper 3 bytes */
    dev->class = class;
    class >>= 8;

    dev_dbg(&dev->dev, "found [%04x:%04x] class %06x header type %02x\n",
         dev->vendor, dev->device, class, dev->hdr_type);

    /* need to have dev->class ready */
    dev->cfg_size = pci_cfg_space_size(dev);

    /* "Unknown power state" */
    dev->current_state = PCI_UNKNOWN;

    /* Early fixups, before probing the BARs */
    pci_fixup_device(pci_fixup_early, dev);
    /* device class may be changed after fixup */
    class = dev->class >> 8;

    switch (dev->hdr_type) {            /* header type */
    case PCI_HEADER_TYPE_NORMAL:            /* standard header */ 普通pci设备
        if (class == PCI_CLASS_BRIDGE_PCI)
            goto bad;

        //读取中断引脚和中断号

        pci_read_irq(dev);

        //读取base addr config0~6寄存器

        pci_read_bases(dev, 6, PCI_ROM_ADDRESS);

        //读取subsys vendor id 和 subsys id

        pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
        pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device)

        。。。。。

        break;

    case PCI_HEADER_TYPE_BRIDGE:            /* bridge header */ pci桥设备
        if (class != PCI_CLASS_BRIDGE_PCI)
            goto bad;
        
        pci_read_irq(dev);
        dev->transparent = ((dev->class & 0xff) == 1);
        pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
        set_pcie_hotplug_bridge(dev);
        pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);
        if (pos) {
            pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
            pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
        }
        break;

    case PCI_HEADER_TYPE_CARDBUS:           /* CardBus bridge header */
        if (class != PCI_CLASS_BRIDGE_CARDBUS)
            goto bad;
        pci_read_irq(dev);
        pci_read_bases(dev, 1, 0);
        pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
        pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
        break;

    。。。。
}
这个函数读取pci设备的相应寄存器来填充pci_dev结构体,如subsys id 等。


pci_setup_device执行完成,从pci_scan_device返回到pci_scan_single_device,返回值就是分配以及按照实际设备填充好的pci_dev。

在这里需要解析一下pci_read_base函数,以普通pci设备为例。

------》》》》pci_read_bases 参数:pci_dev 6 PCI_ROM_ADDRESS

static void pci_read_bases(struct pci_dev *dev, unsigned int howmany, int rom) 
{
    unsigned int pos, reg; 

    for (pos = 0; pos < howmany; pos++) {
        struct resource *res = &dev->resource[pos];
        reg = PCI_BASE_ADDRESS_0 + (pos << 2);
        pos += __pci_read_base(dev, pci_bar_unknown, res, reg);
    }    

    if (rom) {
        struct resource *res = &dev->resource[PCI_ROM_RESOURCE];
        dev->rom_base_reg = rom; 
        res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH |
                IORESOURCE_READONLY | IORESOURCE_CACHEABLE |
                IORESOURCE_SIZEALIGN;
        __pci_read_base(dev, pci_bar_mem32, res, rom);
    }    
}

pci_read_bases函数工作是读取设备配置头中的BAR0~5,以及扩展rom基地址,来填充pci_dev中的resource数组,这个数组在后面具体pci设备driver注册probe的时候非常重要,probe中会读取出这些地址然后做iormap到内核的虚拟地址空间中,然后就可以直接访问了。这些资源都是pci设备所独有的一些寄存器 内存等。

读取BAR0~5以及ROM地址由__pci_read_base完成,这个函数中读取寄存器的值,首先判断BAR是64位还是32位,然后再读取寄存器的值来获得基址和大小,付给pci_dev中的resource相应数组成员的start和end,这样就完成了对pci_dev的resource数组成员的初始化。



在pci_scan_single_device执行pci_device_add,在pci_device_add中调用pci_init_capabilities初始化这个pci设备拥有的特性,然后将pci_dev的成员bus_list添加到pci_bus的设备链表中。


从pci_scan_single_device返回,回到pci_scan_slot函数,如果dev-》multifunction为1,说明是多功能设备,再扫描剩余的7个功能卡,同样执行scan single device,扫描 分配 填充 添加设备。


最后从pci_scan_slot返回到pci_scan_child_bus,完成256/8=32个循环执行pci_scan_slot,也就完成对根总线上最多的256个功能卡的扫描 分配 填充 添加的操作。


在pci_scan_child_bus中,接下来pcibios_fixup_bus来修正bus结构体中的mem资源和io资源(来自hose)因为在pci_bus_alloc中的mem资源和io资源来自系统默认的0~0xffffffff


接下来会遍历bus上的每一个device,如果device是bridge或者cardbus,则调用pci_scan_bridge来扫描桥设备所连接的次级pci总线的所有设备。


pci_scan_bridge函数是实现pci总线枚举的重要函数,实现了pci总线树的遍历。

--------》》》》pci_scan_bridge 

。。。。。

    if ((buses & 0xffff00) && !pcibios_assign_all_busses() && !is_cardbus && !broken) {
        unsigned int cmax, busnr;
        /*   
         * Bus already configured by firmware, process it in the first
         * pass and just note the configuration.
         */
        if (pass)
            goto out; 
        busnr = (buses >> 8) & 0xFF;

        /*   
         * If we already got to this bus through a different bridge,
         * don't re-add it. This can happen with the i450NX chipset.
         *
         * However, we continue to descend down the hierarchy and
         * scan remaining child buses.
         */
        child = pci_find_bus(pci_domain_nr(bus), busnr);
        if (!child) {
            child = pci_add_new_bus(bus, dev, busnr);
            if (!child)
                goto out; 
            child->primary = buses & 0xFF;
            child->subordinate = (buses >> 16) & 0xFF;
            child->bridge_ctl = bctl;
        }    

        cmax = pci_scan_child_bus(child);
        if (cmax > max) 
            max = cmax;
        if (child->subordinate > max) 
            max = child->subordinate;
    } else {
。。。。。

这里只列举如果是桥设备的情况,这种情况下就会扫描桥设备另一端的bus,如果存在,则添加新的bus,然后调用pci_scan_child_bus再来扫描这个bus的设备,这样循环,最终就会完成对整个pci总线树的设备扫描。

pci_scan_child_bus ===> pci_scan_slot ===> pci_scan_bridge ===> pci_scan_child_bus ===> pci_scan_slot。。。。完成树的遍历。


这样递归遍历完成之后从pci_scan_child_bus返回,返回值就是最大的bus号,返回到pci_scan_bus_parented.从pci_scan_bus_parented返回到pci_scan_bus,返回值就是根总线


在pci_scan_bus中接着调用pci_bus_add_devices,扫描所有总线,将总线设备链表中的设备添加到内核的全局设备链表中,并且添加sysfs和procfs的入口,如果添加了 dev->is_added = 1,还将子总线添加到父总线的总线链表中。


从pci_scan_bus中返回到pcibios_scanbus,然后从pcibios_scanbus返回到pcibios_init,这就完成了整个pci总线树设备的枚举(扫描 分配 填充 添加)。


在pcibios_init函数中完成pcibios_scanbus后,会调用pci_fixup_irqs,这个函数实现了pci设备中断号的分配

--------》》》》pci_fixup_irqs 

void __init
pci_fixup_irqs(u8 (*swizzle)(struct pci_dev *, u8 *), 
           int (*map_irq)(struct pci_dev *, u8, u8))
{
    struct pci_dev *dev = NULL;
    while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
        pdev_fixup_irq(dev, swizzle, map_irq);
    }   
}

pci_get_device参数为PCI_ANY_ID则会遍历获取到cpi总线树上的每一个pci设备,然后调用pdev_fixup_irq.

------》》》》pdev_get_device 参数:pci_dev pci_common_swizzle pcibios_map_irq

static void __init
pdev_fixup_irq(struct pci_dev *dev,
           u8 (*swizzle)(struct pci_dev *, u8 *),
           int (*map_irq)(struct pci_dev *, u8, u8))
{
    u8 pin, slot;
    int irq = 0;

    /* If this device is not on the primary bus, we need to figure out
       which interrupt pin it will come in on.   We know which slot it
       will come in on 'cos that slot is where the bridge is.   Each
       time the interrupt line passes through a PCI-PCI bridge we must
       apply the swizzle function.  */

    pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
    /* Cope with illegal. */
    if (pin > 4)
        pin = 1;

    if (pin != 0) {
        /* Follow the chain of bridges, swizzling as we go.  */
        slot = (*swizzle)(dev, &pin);

        irq = (*map_irq)(dev, slot, pin);
        if (irq == -1)
            irq = 0;
    }
    dev->irq = irq;

    dev_dbg(&dev->dev, "fixup irq: got %d\n", dev->irq);

    /* Always tell the device, so the driver knows what is
       the real IRQ to use; the device does not use it. */
    pcibios_update_irq(dev, irq);
}
首先会读取这个设备的interrupt_pin配置寄存器,来看一下中断引脚号,pci连接器有4个中断引脚A B C D,对应于interrupt_pin的1 2 3 4。

接着会调用pci_common_swizzle函数。

------》》》》pci_common_swizzle 参数:pci_dev pin

u8 pci_common_swizzle(struct pci_dev *dev, u8 *pinp)
{
    u8 pin = *pinp;

    while (!pci_is_root_bus(dev->bus)) {
        pin = pci_swizzle_interrupt_pin(dev, pin);
        dev = dev->bus->self;
    }
    *pinp = pin;
    return PCI_SLOT(dev->devfn);
}
如果不是根总线上的设备,也就是说是过桥的设备,需要调用pci_swizzle_interrupt_pin来修正中断引脚号

-----》》》》pci_swizzle_interrupt_pin

u8 pci_swizzle_interrupt_pin(struct pci_dev *dev, u8 pin)
{
    int slot;

    if (pci_ari_enabled(dev->bus))
        slot = 0;
    else
        slot = PCI_SLOT(dev->devfn);

    return (((pin - 1) + slot) % 4) + 1;
}
根据slot号以及从interrupt 品读出的pin号重新计算pin号。返回新的pin号到pci_common_swizzle。由pci_common_swizzle返回到pdev_fixup_irq,pin变量中就是新修正的引脚号,接着调用pcibios_map_irq来修正中断号。

pcibios_map_irq需要根据具体的pci设备使用的引脚号 pci控制器的4个中断引脚的接法以及中断控制器中中断号的分配来决定的,不同的mips处理器开发板有所不同,这里不再详写。


通过pcibios_map_irq获取到重新分配的irq后,将其付给pci_dev的irq成员,然后调用pcibios_update_irq来写入interrupt_pin配置寄存器,给后面的pci设备驱动读取使用。


这样在pci_fixup_irqs中遍历所有的pci设备,调用pci_common_swizzle和pcibios_map_irq给pci设备分配pin和irq,完成后回到pcibios_init。


这样整个的pci总线控制器的注册 pci总线设备的枚举 分配 初始化 链表添加 以及中断引脚 中断号的分配都完成了!

pci_bus结构体中的设备链表以及内核设备链表就座位后面具体的pci设备在pci_driver_register时匹配device的依据。具体driver和device匹配的原则,函数如下:

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值