Linux下PCI设备驱动开发详解(二)

这篇具有很好参考价值的文章主要介绍了Linux下PCI设备驱动开发详解(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Linux下PCI设备驱动开发详解(二)

根据上一章的概念,PCI驱动包括PCI通用的驱动,以及根据实际需要设备本身的驱动。

所谓的编写设备驱动,其实就是编写设备本身驱动,因为linux内核的PCI驱动是内核自带的。

为了更好的学习PCI设备驱动,我们需要明白内核具体做了什么,下面我们研究一下,linux PCI通用的驱动到底做了什么?

注:代码对应的 kernel-3.10.1

一、PCI 拓扑架构

1.1 PCI的系统拓扑

在分析PCIe初始化枚举流程之前,先描述下PCIe的拓扑结构。
如下图所示:

        linux pcie设备树,PCI设备驱动开发详解,linux,驱动开发,c语言,fpga开发

整个PCIe是一个树形的拓扑:

(1) root complex是树的根,它一般实现了一个主桥设备(host bridge),一条内部PCIe总线bus0,以及通过若干PCI bridge扩展出一些root port。host bridge可以完成CPU地址总线到PCI域地址的转换,pci bridge用于系统扩展,没有地址转换功能;

(2) switch是转换设备,目的是扩展PCIe总线。switch中有一个upstream port和若干个downstream port,每个端口相当于一个pci bridge;

(3) PCIe EP device是叶子节点设备,比如PCIe网卡,显卡。NVMe卡等;

1.2 PCIe的软件框架

PCIe模块涉及到的代码文件很多,在分析PCIe的代码前,先对PCIe涉及的代码梳理如下:
这里以arm架构为例,PCIe代码主要分散在3个目录:

drivers/pci/*
drivers/acpi/pci/*
arch/arm/match-xxx/pci.c

将PCIe代码按照如下层次划分:

linux pcie设备树,PCI设备驱动开发详解,linux,驱动开发,c语言,fpga开发

arch PCIe driver:放一些和架构强相关的PCIe的函数实现,对应arch/arm/xxx/pci.c

acpi PCIe driver: acpi扫描时所涉及的PCIe代码,包括host bridge的解析初始化,PCIe bus的创建,ecam的映射等,对应drivers/acpi/pci*.c

PCIe core driver:PCIe的子系统代码,包括PCIe的枚举流程,资源分配流程,中断流程等,主要对应drivers/pci/*.c

PCIe port bus driver:PCIe port的四个service代码的整合,四个service主要是指PCIe dpc/pme/aer/hp,对应drivers/pci/pcie/*

PCIe ep driver:叶子节点的设备驱动,比如显卡、网卡、NVMe;

二、Linux内核实现

PCIe的代码文件这么多,初始化涉及的调用也很多,从哪里开始看呢?

1. PCIe初始化流程

内核通过initcore的level决定模块的启动顺序:

cat System.map |grep pci|grep initcall

可以看出关键symbol的调用顺序如下:

linux pcie设备树,PCI设备驱动开发详解,linux,驱动开发,c语言,fpga开发

pcibus_class_init:注册pci_bus_class,完成后创建了/sys/class/pci_bus目录;

pci_driver_init:注册pci_bus_type,完成后创建了/sys/bus/pci目录;

acpi_pci_init:注册acpi_pci_bus,并设置电源管理相应的操作;

acpi_init():acpi启动所涉及到的初始化流程,PCIe基于acpi的启动流程从该接口进入;

下面对acpi_init()流程展开,主要找和PCI初始化相关的调用:

static int __init acpi_init(void)
{
    ...
    pci_mmcfg_late_init();
    acpi_scan_init();
        ...
        acpi_pci_root_init();
            ...
            static struct acpi_scan_handler pci_root_handler = {
                .ids = root_device_ids,
                .attach = acpi_pci_root_add,
                .detach = acpi_pci_root_remove,
            }
	    acpi_pci_link_init();
	    acpi_platform_init();
	    acpi_lpss_init();
	    acpi_container_init();
	    acpi_memory_hotplug_init();
	    acpi_dock_init();
        ...
    acpi_ec_init();
    acpi_debugfs_init();
    acpi_sleep_proc_init();
    acpi_wakeup_device_init();
    ...
}

mmcfg_late_init():acpi先扫描MCFG表,MCFG表定义了ecam的相关资源;

acpi_pci_root_init():定义pcie host bridge device的attach函数,ACPI的definition block中使用PNP0A03表示一个PCI host bridge;

acpi_pci_link_init():注册pci_link_handler,主要和PCIe IRQ相关;

acpi_bus_scan():会通过acpi_walk_namespace()遍历system中所有的device,并为这些acpi device创建数据结构,执行对应device的attach函数。根据ACPI spec定义,PCIe host bridge device定义在DSDT表中,acpi在扫描中扫描DSDT,如果发现了PCIe host bridge,就会执行device对应的attach函数,调用acpi_pci_root_add();

acpi_pci_root_add():

(1)通过ACPI的SEG参数,获取host bridge使用的segment号,segment指的是PCIe domain,主要目的是为了突破PCIe最大256条bus的限制;

(2)通过ACPI的CRS里的bus range类型资源取得该host bridge的secondary总线范围,保存在root->secondary这个resource中;

(3)通过ACPI的BNN参数获取host bridge的根总线号;
printk(KERN_INFO PREFIX "%s [%s] (domain %04x %pR)\n",
	       acpi_device_name(device), acpi_device_bid(device),
	       root->segment, &root->secondary);

以上流程主要是获取PCI设备的bdf号;

1. PCIe枚举流程

我们先看内核代码:

struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
{
    struct acpi_device *device = root->device;
    struct pci_root_info *info = NULL;
    int domain = root->segment;
    int busnum = root->secondary.start;
    ...
    if (!setup_mcfg_map(info, domain, (u8)root->secondary.start, 
        (u8)root->secondary.end, root->mcfg_addr)) 
        bus = pci_create_root_bus(NULL,busnum, &pci_root_ops, sd, &resources);
  
    ...
}

这个函数主要是建立ecam映射,将ecam的空间进行映射,这样cpu就可以通过内存访问到相应设备的配置空间;

pci_create_root_bus():用来创建该{segment: busnr}下的根总线。传递的参数:

NULL:host bridge设备的parent节点;

busnum:总线号;

pci_root_ops:配置空间的操作接口;

resource:私有数据,用来保存总线号,IO空间,mem空间等信息;

以下依次函数调用是:

pci_scan_child_bus()
    +-> pci_scan_child_bus_extend()
        +-> for dev range(0, 256)
            pci_scan_slot()
                +-> pci_scan_single_device()
                    +-> pci_scan_device()
                        +-> pci_bus_read_dev_vendor_id()
                        +-> pci_alloc_dev()
                        +-> pci_setip_device()
                    +-> pci_add_device()
            
                +-> for each pci bridge
                    +-> pci_scan_bridge_extend()

更详细的分析请参见后面的参考资料

总的来说,枚举流程分为3步:

1.  发现主桥设备和根总线
2.  发现主桥设备下的所有PCI设备
3.  如果主桥下面的是PCI bridge,那么再次遍历这个PCI bridge桥下的所有PCI设备,依次递归,直到将当前PCI总线树遍历完毕,返回host bridge的subordinate总线号。

3. PCIe的资源分配

PCIe设备枚举完成后,PCI总线号已经分配,PCIe ecam的映射、PCIe设备信息、bar的个数以及大小等已经ready,但是此时并没有给PCI device的bar、IO、mem分配资源。

这时就需要走到PCIe的资源分配流程,整个资源分配的过程就是从系统的总资源里给每个PCI device的bar分配资源。给每个PCI桥的base、limit的寄存器分配资源。

PCIe的资源分配流程整体比较复杂,主要介绍下总体的流程,对关键的函数再做展开。

PCIe资源分配的入口在pci_acpi_scan_root()->pci_bus_assign_resources(),详细代码如下:

void __ref __pci_bus_assign_resources(const struct pci_bus *bus,
				      struct list_head *realloc_head,
				      struct list_head *fail_head)
{
	struct pci_bus *b;
	struct pci_dev *dev;

	pbus_assign_resources_sorted(bus, realloc_head, fail_head);

	list_for_each_entry(dev, &bus->devices, bus_list) {
		b = dev->subordinate;
		if (!b)
			continue;

		__pci_bus_assign_resources(b, realloc_head, fail_head);

		switch (dev->class >> 8) {
		case PCI_CLASS_BRIDGE_PCI:
			if (!pci_is_enabled(dev))
				pci_setup_bridge(b);
			break;

		case PCI_CLASS_BRIDGE_CARDBUS:
			pci_setup_cardbus(b);
			break;

		default:
			dev_info(&dev->dev, "not setting up bridge for bus "
				 "%04x:%02x\n", pci_domain_nr(b), b->number);
			break;
		}
	}
}

其中pbus_assign_resources_sorted,这个函数先对当前总线下设备请求的资源进行排序。

总而言之,PCIe的资源枚举过程可以概括为如下:

1. 获取上游PCI桥设备所管理的系统资源范围;
2. 使用DFS对所有的pci ep device进行bar资源的分配;
3. 使用DFS对当前PCI桥设备的base limit的值,并对这些寄存器更新;

四、总结

1. 枚举过程

主要是发现设备,主要流程如下:

1.  发现主桥设备和根总线
2.  发现主桥设备下的所有PCI设备
3.  如果主桥下面的是PCI bridge,那么再次遍历这个PCI bridge桥下的所有PCI设备,依次递归,直到将当前PCI总线树遍历完毕,返回host bridge的subordinate总线号。

2. 资源分配过程

主要是管理设备,方便我们使用设备,主要流程如下:

1. 获取上游PCI桥设备所管理的系统资源范围;
2. 使用DFS对所有的pci ep device进行bar资源的分配;
3. 使用DFS对当前PCI桥设备的base limit的值,并对这些寄存器更新;

五、未完待续

Linux下PCI设备驱动开发详解(三),从内核角度来说,一切皆文件,下面从总线、设备、驱动的角度,详细看一下PCI设备如何变成文件的。

四、参考资料

https://blog.csdn.net/kunkliu/article/details/108950970

<PCI Express Base Specification Revision 5.0, Version 1.0>

https://pcisig.com/文章来源地址https://www.toymoban.com/news/detail-803640.html

到了这里,关于Linux下PCI设备驱动开发详解(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux驱动开发:设备树dts详解

    前言: 掌握设备树是 Linux 驱动开发人员必备的技能!因为在新版本的 Linux 中, ARM 相关的驱动全部采用了设备树(也有支持老式驱动的,比较少),最新出的 CPU 其驱动开发也基本都是基于设备树的,比如 ST 新出的 STM32MP157、NXP 的 I.MX8 系列等。本篇博客核心是系统性的学习设

    2024年02月17日
    浏览(49)
  • pcie 的bdf 详细介绍,及用法实例、linux 查看pci设备信息命令详解

    PCIe是指PCI Express,是一种计算机总线标准。在PCIe中,每个连接到主板上的设备都有唯一的地址,被称为BDF。 BDF:Bus、Device、Function 的缩写。其中Bus是指PCIe总线编号(一个系统中可能存在多个PCIe总线),Device是指连接到该总线上的某个设备编号,Function是指同一个设备上不同

    2024年02月16日
    浏览(28)
  • 宋宝华《Linux设备驱动开发详解》 宋老师csdn上分享的网盘已失效,特意新增的。

    https://pan.baidu.com/s/1rCbRUmnDtjE4jHNB5eQ8CQ 提取码:t12r 案例代码: https://pan.baidu.com/s/1lSMGxLnEFwO0aJGORqx8Og 提取码: bglj Linux设备驱动开发详解:基于最新的Linux4.0内核 (电子与嵌入式系统设计丛书) (宋宝华 著)电子书: https://download.csdn.net/download/MINGTING1323/86831055

    2024年02月13日
    浏览(34)
  • Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134533533 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:《Linux驱动开发笔记(三

    2024年02月05日
    浏览(37)
  • 4、Linux驱动开发:设备-设备号&设备号注册

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(45)
  • Linux驱动开发实战(一)——设备驱动模型

    在早期的Linux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Linux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。 设备驱动模型

    2024年02月16日
    浏览(34)
  • 正点原子嵌入式linux驱动开发——Linux 网络设备驱动

    网络驱动是linux里面驱动三巨头之一 ,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面已经讲过了字符设备驱动和块设备驱动,本章就来学习一下linux里面的 网络设备驱动 。 本次笔记中讨论的都是有线网络! 提起网络,一般想到的硬件就是“网卡”。在

    2024年01月17日
    浏览(55)
  • 深入探讨Linux驱动开发:Linux设备树

    设备树(Device Tree,简称 DT)是一种在嵌入式系统中描述硬件设备的一种数据结构和编程语言。它用于将硬件设备的配置信息以树形结构的方式进行描述,以便操作系统(如 Linux)可以根据这些信息正确地识别、配置和管理硬件设备。 设备树最初被引入到 Linux 内核中,用于解

    2023年04月27日
    浏览(36)
  • Linux设备驱动开发 - 虚拟时钟Clock驱动示例

    By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 很多设备里面系统时钟架构极其复杂,让学习Clock驱动的盆友头大。这里我参考S3C2440的clock驱动写了一个virtual clock,即虚拟时钟驱动,分别包含clock的provider和

    2023年04月21日
    浏览(30)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(22)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包