Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程

21 篇文章 233 订阅

目录

前言

1.枚举过程

1.1 acpi_pci_root_add

1.2 pci_acpi_scan_root(枚举开始)

1.3 acpi_pci_root_create

1.4 pci_scan_child_bus(枚举执行的重点函数)

1.5 pci_scan_slot(pci_scan_single_device才是做事的)

1.6 pci_scan_device

1.7 pci_device_add

1.8 pci_scan_bridge

2.总结


 

前言

在旧版本的内核(以2.6.16版本为例)中系统会调用pci_legacy_init(内核加载等级为4)中调用
pcibios_scan_root来完成对于PCI设备的枚举过程,但是对于现在的x86架构的SOC来说由于ACPI机制的普遍支持,所以对于PCI设备枚举的整个过程就移交至ACPI中来完成。在此我们只基于新机制下的PCI设备枚举的过程进行分析。

注:本文档分析的PCI总线驱动框架基于4.4版本内核

1.枚举过程

由于在ACPI机制在x86平台的广泛应用,所以对于PCI设备探测枚举这个过程也放到了这里来实现,由于PCI设备的枚举只是ACPI要实现的一个功能之一,所以在此我们只对ACPI机制中PCI设备探测这部分进行分析。

我们先来看一个整体的函数流程:

 这些函数向我们描述了ACPI初始化中PCI的相关操作,主要可分为两个部分:

第一部分:acpi_pci_root_init完成PCI设备的相关操作(包括PCI主桥,PCI桥、PCI设备的枚举,配置空间的设置,总线号的分配等);

第二部分:acpi_pci_link_init完成PCI中断的相关操作,在此不做具体分析。

1.1 acpi_pci_root_add

static int acpi_pci_root_add(struct acpi_device *device,
			     const struct acpi_device_id *not_used)
{
    ......
	negotiate_os_control(root, &no_aspm);
	root->bus = pci_acpi_scan_root(root);
	//错误检验
	if (!root->bus) {
		dev_err(&device->dev,
			"Bus %04x:%02x not present in PCI namespace\n",
			root->segment, (unsigned int)root->secondary.start);
		device->driver_data = NULL;
		result = -ENODEV;
		goto remove_dmar;
	}
    ......
}

函数分析:该函数通过ACPI表中的_SEG和_BBN参数获得HOST主桥的Segment和Bus号,创建acpi_pci_root结构(表示HOST主桥信息),在本系统中,由于只有一个HOST主桥,所以acpi_pci_root_add只调用一次,acpi_pci_root也就一个。最终,在完成数据结构的创建以及一些初始化后,就调用pci_acpi_scan_root函数对这条主桥下的PCI节点进行遍历。

1.2 pci_acpi_scan_root(枚举开始)

struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
	int domain = root->segment;
	int busnum = root->secondary.start;
	int node = pci_acpi_root_get_node(root);
	struct pci_bus *bus;

    ......

	bus = pci_find_bus(domain, busnum);   
	if (bus) {                              //如果当前总线存在就不会进行create,直接就返回
		struct pci_sysdata sd = {
			.domain = domain,
			.node = node,
			.companion = root->device
		};

		memcpy(bus->sysdata, &sd, sizeof(sd));
	} else {
		struct pci_root_info *info;

		info = kzalloc_node(sizeof(*info), GFP_KERNEL, node);  //数据结构实体化
		if (!info)
			dev_err(&root->device->dev,
				"pci_bus %04x:%02x: ignored (out of memory)\n",
				domain, busnum);
		else {
			info->sd.domain = domain;
			info->sd.node = node;
			info->sd.companion = root->device;
			bus = acpi_pci_root_create(root, &acpi_pci_root_ops,
						   &info->common, &info->sd);
		}
	}

    ......

	return bus;
}

函数分析:这个函数先调用pci_find_bus去判断当前总线号是否已经存在,如果存在就退出,如果不存在就调用acpi_pci_root_create函数去对这条PCI总线进行遍历,在这里我们需注意一个结构就是acpi_pci_root_ops,它是新版本的内核提供给我们对于PCI信息的一些接口函数的集合(这其中就包括对配置空间的读写方法)

1.3 acpi_pci_root_create

struct pci_bus *acpi_pci_root_create(struct acpi_pci_root *root,
				     struct acpi_pci_root_ops *ops,
				     struct acpi_pci_root_info *info,
				     void *sysdata)
{
	int ret, busnum = root->secondary.start;
	struct acpi_device *device = root->device;
	int node = acpi_get_node(device->handle);
	struct pci_bus *bus;

    ......    //此段代码都对资源的设置初始化和添加操作。
	
    bus = pci_create_root_bus(NULL, busnum, ops->pci_ops, 
				  sysdata, &info->resources);   //创建host桥的数据结构    
	if (!bus)
		goto out_release_info;

	pci_scan_child_bus(bus);  //遍历host桥产生的子总线,从bus 0 开始
	pci_set_host_bridge_release(to_pci_host_bridge(bus->bridge),
				    acpi_pci_root_release_info, info);
	if (node != NUMA_NO_NODE)
		dev_printk(KERN_DEBUG, &bus->dev, "on NUMA node %d\n", node);
	return bus;

out_release_info:
	__acpi_pci_root_release_info(info);
	return NULL;
}

函数分析:在进入此函数时,系统首先会对这个root的信息进行一些初始化和添加操作,之后将这些resources、读写方法、总线号等数据传入pci_create_root_bus这个函数,通过这个函数返回一个总线结构pci_bus(注意:pci_create_root_bus返回的pci总线是总线号为0的总线,即直连HOST桥的总线),最后将得到的结构体送入pci_scan_child_bus开始从总线0进行遍历。

1.4 pci_scan_child_bus(枚举执行的重点函数)

unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
	unsigned int devfn, pass, max = bus->busn_res.start;
	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);

	if (!bus->is_added) {
		dev_dbg(&bus->dev, "fixups for bus\n");
		pcibios_fixup_bus(bus);
		bus->is_added = 1;
	}

	//为了兼容x86和其他不使用BIOS的架构的CPU,执行两次
	for (pass = 0; pass < 2; pass++)   
		list_for_each_entry(dev, &bus->devices, bus_list) {
			if (pci_is_bridge(dev))    //如果是总线上有桥设备就递归调用
				max = pci_scan_bridge(bus, dev, max, pass);
		}

	dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
	return max;
}

函数分析:我们先来说明一下这个函数整体的实现过程:通过for循环先遍历当前总线上的每一个PCI设备(包括PCI桥设备),遍历后将其注册为pci_dev结构体,之后通过递归调用来进入当前总线的下一级子总线继续完成上述过程,直到某条PCI总线上无PCI桥设备,返回最大的总线号。在这个函数中,一上来就是探测总线0上的所有设备,这里通过一个for循环来实现(为什么是0x100,回答:0x100即256,由于一条PCI总线最多可以有32个设备,而每个设备最多能有8个功能,故32*8=256,一条总线最多有256个逻辑设备,那就执行256次把它们一个不漏的全部枚举一次,去发现他们GO),在本函数中,使用pci_scan_slot来遍历探测总线上的每一个设备,使用pci_scan_bridge递归调用pci_scan_child_bus进入下一级总线。先分析pci_scan_slot。

1.5 pci_scan_slot(pci_scan_single_device才是做事的)

这个函数中真正干活的函数是调用pci_scan_single_device函数实现的,这里就不贴代码了,直接进入pci_scan_single_device函数去。

struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
	struct pci_dev *dev;

    ......

	dev = pci_scan_device(bus, devfn);  //pci_dev的建立
	if (!dev)
		return NULL;

	pci_device_add(dev, bus); //pci_dev的注册

	return dev;
}

函数分析:在pci_scan_single_device函数进一步调用了两个函数pci_scan_device函数和pci_device_add函数,先说说这两个函数是干啥的,首先pci_scan_device函数完成的事情是依据BIOS遍历的结果去填充pci_dev结构,而pci_device_add函数的工作就简单了,把这个结构体加到当前PCI总线的设备链表上去,然后注册设备。一个个分析。

1.6 pci_scan_device

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

    //读取该PCI设备的厂商ID和设备ID,如果连这两个ID都读取无效,就直接返回
	if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))  
		return NULL;

	dev = pci_alloc_dev(bus);      //创建一个pci_dev结构
	if (!dev)
		return NULL;

	dev->devfn = devfn;                     //把刚刚获取的两个ID填充至结构体(就是靠这两个ID去找驱动的)
	dev->vendor = l & 0xffff;
	dev->device = (l >> 16) & 0xffff;

	pci_set_of_node(dev);

	if (pci_setup_device(dev)) {           //进一步填充pci_dev结构,本函数重点
		pci_bus_put(dev->bus);
		kfree(dev);
		return NULL;
	}

	return dev;
}

函数分析:这个函数做了哪些事情,我们已在代码中列举了,下面直接看这个函数的重点pci_setup_device(dev)

int pci_setup_device(struct pci_dev *dev)
{
	u32 class;
	u16 cmd;
	u8 hdr_type;
	int pos = 0;
	struct pci_bus_region region;
	struct resource *res;

	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;
	dev->hdr_type = hdr_type & 0x7f;  //依据头信息,决定这个设备属于哪类PCI设备
	dev->multifunction = !!(hdr_type & 0x80);
	dev->error_state = pci_channel_io_normal;
	set_pcie_port_type(dev);

	pci_dev_assign_slot(dev);

	dev->dma_mask = 0xffffffff;

	//设置设备名字 主桥号(一般为0):PCI总线号:PCI设备号.PCI设备功能号
	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;
	dev->class = class >> 8;		    /* upper 3 bytes */

	dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %02x class %#08x\n",
		   dev->vendor, dev->device, dev->hdr_type, dev->class);

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

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

	pci_msi_setup_pci_dev(dev);

	/* 做一个早期的检查 */
	pci_fixup_device(pci_fixup_early, dev);
	/* device class may be changed after fixup */
	class = dev->class >> 8;

	if (dev->non_compliant_bars) {
		pci_read_config_word(dev, PCI_COMMAND, &cmd);
		if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
			dev_info(&dev->dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
			cmd &= ~PCI_COMMAND_IO;
			cmd &= ~PCI_COMMAND_MEMORY;
			pci_write_config_word(dev, PCI_COMMAND, cmd);
		}
	}
*********************************************************************************
	switch (dev->hdr_type) {		    /* header type */
	case PCI_HEADER_TYPE_NORMAL:		    //标准配置空间的类型
		if (class == PCI_CLASS_BRIDGE_PCI)  //桥设备(0604)
			goto bad;
		pci_read_irq(dev);                   //读取中断 

        //读取BAR寄存器中所存的基地址以及这些区间的大小
		pci_read_bases(dev, 6, PCI_ROM_ADDRESS);   

        //读取subsystem vendor ID
		pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);  

        //读取subsystem device ID
		pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);    

		if (class == PCI_CLASS_STORAGE_IDE) {  //IDE控制器设备(0101)
			u8 progif;
			pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);
			if ((progif & 1) == 0) {        //不同的编程接口
				region.start = 0x1F0;
				region.end = 0x1F7;
				res = &dev->resource[0];
				res->flags = LEGACY_IO_RESOURCE;
				pcibios_bus_to_resource(dev->bus, res, &region);
				dev_info(&dev->dev, "legacy IDE quirk: reg 0x10: %pR\n",
					 res);
				region.start = 0x3F6;
				region.end = 0x3F6;
				res = &dev->resource[1];
				res->flags = LEGACY_IO_RESOURCE;
				pcibios_bus_to_resource(dev->bus, res, &region);
				dev_info(&dev->dev, "legacy IDE quirk: reg 0x14: %pR\n",
					 res);
			}
			if ((progif & 4) == 0) {
				region.start = 0x170;
				region.end = 0x177;
				res = &dev->resource[2];
				res->flags = LEGACY_IO_RESOURCE;
				pcibios_bus_to_resource(dev->bus, res, &region);
				dev_info(&dev->dev, "legacy IDE quirk: reg 0x18: %pR\n",
					 res);
				region.start = 0x376;
				region.end = 0x376;
				res = &dev->resource[3];
				res->flags = LEGACY_IO_RESOURCE;
				pcibios_bus_to_resource(dev->bus, res, &region);
				dev_info(&dev->dev, "legacy IDE quirk: reg 0x1c: %pR\n",
					 res);
			}
		}
		break;

	case PCI_HEADER_TYPE_BRIDGE:		    // 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桥配置空间的类型
		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;

	default:				    //不知道什么类型,错误
		dev_err(&dev->dev, "unknown header type %02x, ignoring device\n",
			dev->hdr_type);
		return -EIO;

	bad:
		dev_err(&dev->dev, "ignoring class %#08x (doesn't match header type %02x)\n",
			dev->class, dev->hdr_type);
		dev->class = PCI_CLASS_NOT_DEFINED << 8;
	}

	/* We found a fine healthy device, go go go... */ 
	return 0;
}

函数分析:这个函数好长,我们先总体说明该的功能,即:继续初始化pci_dev结构体的版本号,类型,存储空间,中断线等问题,在代码中,我用一条线将函数分成两个部分。在上半部就是一些常规的操作(从配置空间读取一些信息例如:配置空间类型,设备类型等,将读取到的信息填充到设备结构体,做一些早期的参数修正)。在下半部中函数通过switch语句将设备的配置空间分为三类即:标准类型(6个BAR)、桥类型(2个BAR)、CardBus桥类型(1个BAR),当一个设备在准备遍历的时候会依据该设备配置空间的信息来确定这是个什么设备(例如:读取配置空间的Class code寄存器,得到一个值0x0101表示这是一种大容量存储控制器且使用的是IDE控制器,查表可得)依据不同的设备类型,来获取配置空间的信息填充pci_dev结构。由此我们可知,本函数看似很长,其实内部做的更多的只是依据不同类型的设备去为pci_dev结构填充数据而已。

1.7 pci_device_add

void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
{
	int ret;

	pci_configure_device(dev);

	device_initialize(&dev->dev);
	dev->dev.release = pci_release_dev;

	set_dev_node(&dev->dev, pcibus_to_node(bus));
	dev->dev.dma_mask = &dev->dma_mask;
	dev->dev.dma_parms = &dev->dma_parms;
	dev->dev.coherent_dma_mask = 0xffffffffull;
	pci_dma_configure(dev);

	pci_set_dma_max_seg_size(dev, 65536);
	pci_set_dma_seg_boundary(dev, 0xffffffff);

	pci_fixup_device(pci_fixup_header, dev);       

	pci_reassigndev_resource_alignment(dev);

	dev->state_saved = false;

	pci_init_capabilities(dev);

	down_write(&pci_bus_sem);
	list_add_tail(&dev->bus_list, &bus->devices);     //将该设备加入到总线的设备链表中
	up_write(&pci_bus_sem);

	ret = pcibios_add_device(dev);
	WARN_ON(ret < 0);
	pci_set_msi_domain(dev);

	dev->match_driver = false;
	ret = device_add(&dev->dev);       //添加kobject
	WARN_ON(ret < 0);
}

函数分析:本函数所做的事情没什么好说的,就是对pci_dev做一些最后的填充,修正后,添加到当前PCI总线的设备链表中,并且调用device_add函数注册相应的kobject。

1.8 pci_scan_bridge

int pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, int pass)
{
	struct pci_bus *child;
	int is_cardbus = (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS);
	u32 buses, i, j = 0;
	u16 bctl;
	u8 primary, secondary, subordinate;
	int broken = 0;

    ......

	if ((secondary || subordinate) && !pcibios_assign_all_busses() &&
	    !is_cardbus && !broken) {
		unsigned int cmax;

		if (pass)
			goto out;

		child = pci_find_bus(pci_domain_nr(bus), secondary);    //检查该bus是否存在
		if (!child) {
			child = pci_add_new_bus(bus, dev, secondary);
			if (!child)
				goto out;
			child->primary = primary;
			pci_bus_insert_busn_res(child, secondary, subordinate);
			child->bridge_ctl = bctl;
		}

		cmax = pci_scan_child_bus(child);          //递归调用,进入到下一级PCI BUS
		if (cmax > subordinate)
			dev_warn(&dev->dev, "bridge has subordinate %02x but max busn %02x\n",
				 subordinate, cmax);
		/* subordinate should equal child->busn_res.end */
		if (subordinate > max)
			max = subordinate;
	} else {
	    ......
	}

	sprintf(child->name,
		(is_cardbus ? "PCI CardBus %04x:%02x" : "PCI Bus %04x:%02x"),
		pci_domain_nr(bus), child->number);

        ......
				&bus->busn_res);
		}
		bus = bus->parent;
	}

out:
	pci_write_config_word(dev, PCI_BRIDGE_CONTROL, bctl);

	return max;
}

函数分析:本函数调用了两次(通过for循环),原因是:在不同架构的对于PCI设备的枚举实现是不同的,例如在x86架构中有BIOS为我们提前去遍历一遍PCI设备,但是在ARM或者powerPC中uboot是没有这种操作的,所以为了兼容这两种情况,这里就执行两次对应于两种不同的情况,当pci_scan_slot函数执行完了后,我们就得到了一个当前PCI总线的设备链表,在执行pci_scan_bridge函数前,会遍历这个设备链表,如果存在PCI桥设备,就调用pci_scan_bridge函数,而在本函数内部会再次调用pci_scan_child_bus函数,去遍历子PCI总线设备(注意:这时的BUS就已经不是PCI BUS 0了)就是通过这种一级一级的递归调用,在遍历总PCI总线下的每一条PCI子总线。直到某条PCI子总线下无PCI桥设备,就停止递归,并修改subbordinate参数,(最大PCI总线号)返回。

2.总结

至此,PCI总线的枚举过程执行完毕,经历了上述这些函数,我们已经得到了PCI总线下的所有设备的信息,并将它们注册为了pci_dev结构体,这时pci总线上的设备信息已经准备完毕,现在就是等待后续将要注册的PCI驱动与他们进行匹配。梳理了整个过程之后,我们可以了解到,BIOS的枚举:初始化所有PCI设备的配置空间,系统的枚举:依据BIOS枚举后的配置空间信息生成pci_dev设备结构体。

本文转自PCI总线驱动代码梳理(三)--PCI设备的枚举

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
Linux内核中包含了大量的PCI驱动程序,这些驱动程序的功能各不相同,但都是用于支持PCI设备的工作。下面我们来详细分析一些典型的PCI驱动程序。 1. e1000e驱动程序 e1000e驱动程序是用于Intel网卡的驱动程序,它支持Intel 82563/6/7, 82571/2/3/4/7/8/9, or 82583 NICs。e1000e驱动程序采用DMA总线传输机制,能够提供高性能的网络传输。 2. ahci驱动程序 ahci驱动程序是用于SATA硬盘控制器的驱动程序,它支持AHCI(Advanced Host Controller Interface)标准,能够提供高速稳定的数据传输,支持NCQ(Native Command Queuing)和Hot Plug等特性。 3. igb驱动程序 igb驱动程序是用于Intel Gigabit以太网卡的驱动程序,它支持Intel 82575/6, 82580, I350, I210/1, and I211 NICs。igb驱动程序采用DMA总线传输机制,能够提供高性能的网络传输。 4. nvme驱动程序 nvme驱动程序是用于NVMe(NVM Express)SSD固态硬盘的驱动程序,它支持PCIe接口,能够提供高速稳定的数据传输,支持多队列和命令集等特性。 5. mlx4驱动程序 mlx4驱动程序是用于Mellanox Connect-IB/Connect-X Infiniband和以太网适配器的驱动程序,它采用DMA总线传输机制,支持InfiniBand和Ethernet网络通信协议,能够提供高性能的网络传输。 总之,Linux内核中的PCI驱动程序功能丰富,能够支持各种类型的PCI设备,为系统的性能和稳定性提供了重要的保障。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸鱼弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值