Linux PCI驱动学习小结--以8250.c文件为例子

PCI学习小结


这段时间学习了下Linux下PCI子系统和如何编写自己的PCI模块。
总的来说,PCI协议清晰还是容易理解的,Linux对PCI子系统的封装也比较好,几乎不用我们接触控制PCI底层。

PCI规范

PCI设备内部都有一个配置空间,大小为256字节的寄存器,其中配置空间头部64字节是PCI标准规定的,剩下的都是厂商自定义
PCI配置空间
这些都是描述信息,储存着PCI告诉操作系统的主要信息,比如PCI设备是Intel生产的,型号是XXXXX,需要100M的缓存空间。这些信息都是由Linux设备驱动模型中的驱动(Driver)进行处理,由驱动读取PCI配置空间内的信息,再向的Linux内核申请PCI需要的资源。

配置空间中的大部分信息通过名字就知道什么意思了,这里不深入解释了,后面分析驱动的时候用到了再说。
说一下Base Address Registers,Base Address Registers记录了设备所需要的地址空间的类型(memory space或者I/O space),基址以及其他属性。Base Address Registers的格式如下:
在这里插入图片描述

在这里插入图片描述

这里又不得不提一下memory space或者I/O space,我并不是很了解X86架构,但是在ARM芯片的手册上,我们可以发现内存,以及外设全都位于总线上,共享分割总线的地址。
比如:0xff000000-0x00000000是控制(外设)GPIO的,剩余的是RAM的地址,这就使得本就总线地址空间少的arm,可用RAM更加少了,例如:32位最多4GB的寻址空间,外设占用了2GB,RAM只剩下2GB可用。但是在X86上,外设地址和RAM的地址是独立,也就是说64位的X86CPU,RAM可用总线就是64根,外设可用总线也是64根(打比方而已,实际上外设用不到64根),不存在说要像arm里一样64位要划分32根总线给RAM,32根总线给外设。
RAM对应的就是memory space。
而外设地址就是I/O space。

驱动分析

我这里以Linux内核中8250_pci.c和r8169_main.c文件为例子
分析一下Linux PCI模块的流程
8250_pci.c是PCI转串口的驱动
直接翻到最下面看probe
这是经典的Linux注册驱动的函数,里面最重要的是pciserial_init_oneserial_pci_tbl,分别对应8250_pci PCI驱动的匹配过程以及匹配对象。

static const struct pci_error_handlers serial8250_err_handler = {
	.error_detected = serial8250_io_error_detected,
	.slot_reset = serial8250_io_slot_reset,
	.resume = serial8250_io_resume,
};

static struct pci_driver serial_pci_driver = {
	.name		= "serial",
	.probe		= pciserial_init_one,
	.remove		= pciserial_remove_one,
	.driver         = {
		.pm     = &pciserial_pm_ops,
	},
	.id_table	= serial_pci_tbl,
	.err_handler	= &serial8250_err_handler,
};

module_pci_driver(serial_pci_driver);

先看serial_pci_tbl

struct pci_device_id {
	__u32 vendor, device;		/* Vendor and device ID or PCI_ANY_ID*/
	__u32 subvendor, subdevice;	/* Subsystem ID's or PCI_ANY_ID */
	__u32 class, class_mask;	/* (class,subclass,prog-if) triplet */
	kernel_ulong_t driver_data;	/* Data private to the driver */
};




#define PCI_VENDOR_ID_ADVANTECH		0x13fe
#define PCI_DEVICE_ID_ADVANTECH_PCI3620	0x3620

static const struct pci_device_id serial_pci_tbl[] = {
	/* Advantech use PCI_DEVICE_ID_ADVANTECH_PCI3620 (0x3620) as 'PCI_SUBVENDOR_ID' */
	{	PCI_VENDOR_ID_ADVANTECH, PCI_DEVICE_ID_ADVANTECH_PCI3620,
		PCI_DEVICE_ID_ADVANTECH_PCI3620, 0x0001, 0, 0,
		pbn_b2_8_921600 }
		}

我们结合之前给出的256字节寄存器的配置空间,可以发现serial_pci_tbl里是配置空间的信息,现在知道为什么Linux用pci_device_id配对了吧。

接着我们看probe函数,不必过多深入源码,了解函数作用适可而止即可。
我们先看probe函数的形参
(struct pcdevicei_dev *dev, const struct pci_device_id *ent)
第一个是经典的dev,这个不解释了,看第二个pci_device_id,是不是很熟悉,我们猜测 这个地方就储存着驱动目标设备中配置空间的部分信息

static int
pciserial_init_one(struct pcdevicei_dev *dev, const struct pci_device_id *ent)
{
	pci_dev中包含PCI头信息,如:厂商的信息
	struct pci_serial_quirk *quirk; //私有自定义
	struct serial_private *priv; //私有自定义
	const struct pciserial_board *board;//公开自定义
	const struct pci_device_id *exclude;//一些PCI头信息,如:厂商的信息
	struct pciserial_board tmp;//公开自定义
	int rc;

	quirk = find_quirk(dev);// 匹配各家厂商的PCI信息,比如Intel
	if (quirk->probe) {
		rc = quirk->probe(dev);
		if (rc)
			return rc;
	}

	if (ent->driver_data >= ARRAY_SIZE(pci_boards)) {//私有数据ent->driver_data
		dev_err(&dev->dev, "invalid driver_data: %ld\n",
			ent->driver_data);
		return -EINVAL;
	}

	board = &pci_boards[ent->driver_data];

	exclude = pci_match_id(blacklist, dev); //return NULL
	if (exclude)
		return -ENODEV;

	rc = pcim_enable_device(dev); //启用PCI
	pci_save_state(dev);
	if (rc)
		return rc;

		/*
	if (ent->driver_data == pbn_default) {
		 * Use a copy of the pci_board entry for this;
		 * avoid changing entries in the table.
		 */
		memcpy(&tmp, board, sizeof(struct pciserial_board));
		board = &tmp;

		/*
		 * We matched one of our class entries.  Try to
		 * determine the parameters of this board.
		 */
		rc = serial_pci_guess_board(dev, &tmp);
		if (rc)
			return rc;
	} else {
		/*
		 * We matched an explicit entry.  If we are able to
		 * detect this boards settings with our heuristic,
		 * then we no longer need this entry.
		 */
		memcpy(&tmp, &pci_boards[pbn_default],
		       sizeof(struct pciserial_board));
		rc = serial_pci_guess_board(dev, &tmp);
		if (rc == 0 && serial_pci_matches(board, &tmp))
			moan_device("Redundant entry in serial pci_table.",
				    dev);
	}

	priv = pciserial_init_ports(dev, board);
	if (IS_ERR(priv))
		return PTR_ERR(priv);

	pci_set_drvdata(dev, priv);
	return 0;
}

看一下find_quirk()

static inline int quirk_id_matches(u32 quirk_id, u32 dev_id)
{
	return quirk_id == PCI_ANY_ID || quirk_id == dev_id;
}

static struct pci_serial_quirk *find_quirk(struct pci_dev *dev)// 匹配各家厂商的PCI信息
{
	struct pci_serial_quirk *quirk;

	for (quirk = pci_serial_quirks; ; quirk++)
		if (quirk_id_matches(quirk->vendor, dev->vendor) &&
		    quirk_id_matches(quirk->device, dev->device) &&
		    quirk_id_matches(quirk->subvendor, dev->subsystem_vendor) &&
		    quirk_id_matches(quirk->subdevice, dev->subsystem_device))
			break;
	return quirk;
}

可以看出,dev里也存有PCI设备的配置空间信息。
find_quirk从dev中获取信息并且进行匹配,对应特殊的设备还会进行初始化设定。
pcim_enable_device()启用PCI设备。

在pciserial_init_one()末尾有一serial_pci_guess_board()函数

static int
serial_pci_guess_board(struct pci_dev *dev, struct pciserial_board *board)
{
	int num_iomem, num_port, first_port = -1, i;
	int rc;

	rc = serial_pci_is_class_communication(dev);
	if (rc)
		return rc;

	/*
	 * Should we try to make guesses for multiport serial devices later?
	 */
	if ((dev->class >> 8) == PCI_CLASS_COMMUNICATION_MULTISERIAL)
		return -ENODEV;

	num_iomem = num_port = 0;
	for (i = 0; i < PCI_NUM_BAR_RESOURCES; i++) {
		if (pci_resource_flags(dev, i) & IORESOURCE_IO) {
			num_port++;
			if (first_port == -1)
				first_port = i;
		}
		if (pci_resource_flags(dev, i) & IORESOURCE_MEM)
			num_iomem++;
	}

	/*
	 * If there is 1 or 0 iomem regions, and exactly one port,
	 * use it.  We guess the number of ports based on the IO
	 * region size.
	 */
	if (num_iomem <= 1 && num_port == 1) {
		board->flags = first_port;
		board->num_ports = pci_resource_len(dev, first_port) / 8;
		return 0;
	}

	/*
	 * Now guess if we've got a board which indexes by BARs.
	 * Each IO BAR should be 8 bytes, and they should follow
	 * consecutively.
	 */
	first_port = -1;
	num_port = 0;
	for (i = 0; i < PCI_NUM_BAR_RESOURCES; i++) {
		if (pci_resource_flags(dev, i) & IORESOURCE_IO &&
		    pci_resource_len(dev, i) == 8 &&
		    (first_port == -1 || (first_port + num_port) == i)) {
			num_port++;
			if (first_port == -1)
				first_port = i;
		}
	}

	if (num_port > 1) {
		board->flags = first_port | FL_BASE_BARS;
		board->num_ports = num_port;
		return 0;
	}

	return -ENODEV;
}

可以看出该函数的作用是获取Base Address Registers里的IORESOURCE_IO以及IORESOURCE_MEM,分别对应I/O space,memory space。

接着在pciserial_init_one()的函数中会调用注册串口的函数。

总结一下8250_pci模块的逻辑

  1. 8250_pci.c中用数组储存PCI的部分配置空间的信息,如:厂商,型号。以及各自的初始化回调函数,实现一个驱动支持不同型号的设备
  2. module_pci_driver(serial_pci_driver)向PCI子系统进行驱动注册,注册内容中包含匹配设备后进行初始化的probe函数,以及目标设备的部分配置空间的信息,如:厂商:Intel型号:8250。这样Linux会在发现PCI中含有厂商Intel,型号为8250的设备后,执行我们的probe函数。
  3. 在8250_pci.c中,通过probe中的形参dev进一步对配置空间的信息进行确定,以及提取出配置空间中Base Address Registers的信息。
  4. 将上一步提取的配置空间中Base Address Registers信息,注册进串口子系统,将重点放在串口子系统上。

PCI驱动注册全程没有接触到PCI底层,反而重点在串口子系统的注册上

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值