Linux设备模型初始化——PCI子系统初始化

在《Linux设备模型初始化》一节介绍了由__define_initcall引入的几个初始化宏,PCI初始化过程就是由这些宏控制的,下面我们介绍PCI的初始化。

首先就是优先级最高的postcore_initcall(pcibus_class_init),pcibus_class_init函数是PCI子系统第一个执行的初始化函数。在sys/class中注册pci_bus目录。

static int __init pcibus_class_init(void)
{
	return class_register(&pcibus_class);
}

pcibus_class定义如下

static struct class pcibus_class = {
	.name		= "pci_bus",
	.release	= &release_pcibus_dev,
};

pcibus_class_init就是将pcibus_class注册到/sys/class目录下,创建/sys/class/pci_bus目录

然后subsys_initcall(pci_legacy_init)决定接下来会调用pci_legacy_init

static int __init pci_legacy_init(void)
	if (pcibios_scanned++)
		return 0;
	pci_root_bus = pcibios_scan_root(0);
		//最开始的时候,pci_root_buses为空,所以pci_find_next_bus返回空
		while ((bus = pci_find_next_bus(bus)) != NULL)
			///* 如果现有的根总线编号与传参数相等,说明已经扫描过该根总线,退出 */
			if (bus->number == busnum) {
				/* Already scanned */
				return bus;
			}
		return pci_scan_bus(busnum, &pci_root_ops, NULL); //busnum=0
			return pci_scan_bus_parented(NULL, bus, ops, sysdata);

首先增加pcibios_scanned的值,如果增加前的值不为0,就说明已经初始胡过一次了,就直接退出,否则调用pcibios_scan_root从0号pci线去扫描总线,具体的扫描操作是由pci_scan_bus_parented执行的。

struct pci_bus * __devinit pci_scan_bus_parented(struct device *parent, int bus, struct pci_ops *ops, void *sysdata)
{
	/* 为根总线分配一个pci_bus描述符 */
	b = pci_alloc_bus();
	dev = kmalloc(sizeof(*dev), GFP_KERNEL);

	b->sysdata = sysdata;
	b->ops = ops;

	/* pci_find_bus查找成功,说明通过另外一个桥设备可以到达总线,这样放弃扫描 */
	if (pci_find_bus(pci_domain_nr(b), bus)) 
		goto err_out;
		
	/* 将根总线链接入pci_root_buses链表 */
	list_add_tail(&b->node, &pci_root_buses);

	/* 初始化设备,并注册到系统。 */
	memset(dev, 0, sizeof(*dev));
	dev->parent = parent;
	dev->release = pci_release_bus_bridge_dev;
	sprintf(dev->bus_id, "pci%04x:%02x", pci_domain_nr(b), bus);
	// 如果dev的父设备为空,所以会把dev注册到/dev/devices目录下,否则就会注册到父设备目录下,对于根总线,就是创建/sys/devices/pci0000:00目录
	device_register(dev);
	b->bridge = get_device(dev);

	/* 初始化类设备描述符,并添加到系统 */
	b->class_dev.class = &pcibus_class;
	sprintf(b->class_dev.class_id, "%04x:%02x", pci_domain_nr(b), bus);
	// 将类设备注册到/sys/class/pci_bus/目录下,也就是创建/sys/class/pci_bus/0000:00目录
    class_device_register(&b->class_dev);
	/* 
		在PCI总线目录下创建属性文件,关于class_device_attr_cpuaffinity的定义
		参考CLASS_DEVICE_ATTR(cpuaffinity, S_IRUGO, pci_bus_show_cpuaffinity, NULL);
	*/
	class_device_create_file(&b->class_dev, &class_device_attr_cpuaffinity);

	/* Create legacy_io and legacy_mem files for this bus */
	/* 在PCI总线目录下创建二进制属性文件 */
	pci_create_legacy_files(b);

	// 在类设备目录下创建名为“bridge”的链接文件,链接到对应的桥设备目录下
	sysfs_create_link(&b->class_dev.kobj, &b->bridge->kobj, "bridge");

	/* 初始化总线与次总线编号 */
	b->number = b->secondary = bus;
	/* 设置端口和内存资源,以后分配资源时使用 */
	b->resource[0] = &ioport_resource;
	b->resource[1] = &iomem_resource;

	/* 扫描根总线 */
	b->subordinate = pci_scan_child_bus(b);

	pci_bus_add_devices(b);

	return b;

pci_scan_bus_parented的主要内容如下:
(1)Linux把总线也看做设备,所以将代表根总线的设备注册到/dev/devices目录下,创建创建/sys/devices/pci0000:00目录。
(2)设置根总线的桥设备就是当前总线设备。
(3) 将代表根总线的类设备注册到/sys/class/pci_bus/目录下,也就是创建/sys/class/pci_bus/0000:00目录。
(4)在类设备目录下创建名为“bridge”的链接文件,链接到对应的桥设备目录下。
(5)调用pci_scan_child_bus去扫描根总线。
(6)调用pci_bus_add_devices将扫描到的设备插入到全局PCI设备列表中,并加入到sysfs和procfs中。

unsigned int __devinit pci_scan_child_bus(struct pci_bus *bus)
{
	/**
	 * 扫描当前PCI总线的所有设备,如果设备是PCI总线,会递归调用本函数。
	 * 并将设备加入到对应总线的设备队列中。
	 * 每个PCI总线最多有32个设备,每个设备最多有8个function,因此循环0x100次。
	 */
	for (devfn = 0; devfn < 0x100; devfn += 8)
		pci_scan_slot(bus, devfn);

	/**
	 * pcibios_fixup_bus的主要目的是为一些PCI设备中的errata提供work-around.
	 * 同时还会调用一个重要的函数pci_read_bridge_base函数。
	 * pci_read_bridge_base函数会根据寄存存器的值初始化PCI所管理的地址空间。
	 */
	pcibios_fixup_bus(bus);
	/**
	 * 调用pci_scan_bridge函数处理当前PCI总线上所挂接的PCI桥。
	 */
	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)
			    /**
			     * pass为0处理"已经完成枚举"的PCI桥。
			     * pass为1处理"尚未完成枚举"的PCI桥。
			     * 对x86来说,BIOS已经预先对总线进行枚举。	
			     */
				max = pci_scan_bridge(bus, dev, max, pass);
		}
	return max;
}	

pci_scan_child_bus首先调用pci_scan_slot去扫描挂载到该当前总线的上的设备,然后调用pci_scan_bridge去递归的扫描桥接的其他PCI总线。

int __devinit pci_scan_slot(struct pci_bus *bus, int devfn)
	/**
	 * 每个PCI设备最多可能有8个function。
	 */
	for (func = 0; func < 8; func++, devfn++) {
		struct pci_dev *dev;

		/**
		 * 配置设备。
		 * pci_scan_single_device调用pci_scan_device对PCI设备的配置寄存器进行读写操作,侧重于对PCI设备进行硬件层面的初始化操作。
		 * 调用pci_device_add进行软件方面的初始化。
		 */
		dev = pci_scan_single_device(bus, devfn);
			dev = pci_scan_device(bus, devfn);
			pci_fixup_device(pci_fixup_header, dev);
			/* 将设备添加到全局链表,以及总线设备链表 */
			INIT_LIST_HEAD(&dev->global_list);
			list_add_tail(&dev->bus_list, &bus->devices);
		nr++;
	return nr;

pci_scan_slot会调用pci_scan_single_device去扫描该总线上功能号为devfn的设备,pci_scan_single_device会继续调用pci_scan_device去扫描设备,然后将扫描到的设备利用bus_list添加到bus->devices的链表上。

所以最终扫描指定功能号的设备是否存在就是由pci_scan_device函数去完成的,接下来我们看一下该函数

static struct pci_dev * __devinit
pci_scan_device(struct pci_bus *bus, int devfn)
{
	/* 读取总线上给定设备/功能号的配置空间中厂商ID和设备ID */
	if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
		return NULL;

	/* 某些主板会返回全0或全1表示设备不存在 */
	if (l == 0xffffffff || l == 0x00000000 ||
	    l == 0x0000ffff || l == 0xffff0000)
		return NULL;

	/**
	 * 读取VENDOR_ID和HEADER_TYPE寄存器,并对设备进行初始化。
	 */
	while (l == 0xffff0001) {/* 0xffff0001状态表示配置重试状态 */
		/* 读取配置空间失败,退出 */
		if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))
			return NULL;
	}

	/* 厂商和设备ID读取成功,这里再读取配置头 */
	if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))
		return NULL;

	/**
	 * 分配pci_dev并对其进行初始化。
	 */
	dev = kmalloc(sizeof(struct pci_dev), GFP_KERNEL);
	if (!dev)
		return NULL;

	memset(dev, 0, sizeof(struct pci_dev));
	dev->bus = bus;
	dev->sysdata = bus->sysdata;
	dev->dev.parent = bus->bridge;
	dev->dev.bus = &pci_bus_type;
	dev->devfn = devfn;
	dev->hdr_type = hdr_type & 0x7f;
	dev->multifunction = !!(hdr_type & 0x80);
	dev->vendor = l & 0xffff;
	dev->device = (l >> 16) & 0xffff;
	dev->cfg_size = pci_cfg_space_size(dev);

	dev->dma_mask = 0xffffffff;
	/**
	 * pci_setup_device对设备进行真正的初始化,在该函数里会设备pci设备的名字,例如0000:00:00:0
	 */
	if (pci_setup_device(dev) < 0) {
		kfree(dev);
		return NULL;
	}
	/* 初始化PCI设备的内嵌设备描述符 */
	device_initialize(&dev->dev);
		kobj_set_kset_s(dev, devices_subsys);
	dev->dev.release = pci_release_dev;
	pci_dev_get(dev);

	pci_name_device(dev);

	dev->dev.dma_mask = &dev->dma_mask;
	dev->dev.coherent_dma_mask = 0xffffffffull;

	return dev;

PCI总线扫描结束后,即pci_scan_child_bus调用结束后,会执行pci_bus_add_devices函数去注册扫描到的设备。

void __devinit pci_bus_add_devices(struct pci_bus *bus)
{
	// 遍历该总线上的设备
	list_for_each_entry(dev, &bus->devices, bus_list) {
		if (!list_empty(&dev->global_list))
			continue;
		/**
		 * 将当前PCI总线上的所有PCI设备相关信息加入到proc和sysfs文件系统中。
		 */
		pci_bus_add_device(dev);
			// PCI设备被注册到/sys/devices/pci0000:00目录下
			device_add(&dev->dev);
			list_add_tail(&dev->global_list, &pci_devices);
			pci_proc_attach_device(dev);
			pci_create_sysfs_dev_files(dev);
	}

	list_for_each_entry(dev, &bus->devices, bus_list) {

		if (dev->subordinate && list_empty(&dev->subordinate->node)) {
			spin_lock(&pci_bus_lock);
			list_add_tail(&dev->subordinate->node, &dev->bus->children);
			spin_unlock(&pci_bus_lock);
			/**
			 * 递归调用,将PCI总线上的所有PCI子桥加入proc和sysfs文件系统。
			 */
			pci_bus_add_devices(dev->subordinate);

			sysfs_create_link(&dev->subordinate->class_dev.kobj, &dev->dev.kobj, "bridge");
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值