Linux源码阅读——PCI总线驱动代码(二)配置空间访问设置

2024-09-02 05:48

本文主要是介绍Linux源码阅读——PCI总线驱动代码(二)配置空间访问设置,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.什么是配置空间

2.对配置空间的访问

3.PCI驱动中对配置空间的访问的实现

3.1 pci_direct_probe

3.2 pci_mmcfg_early_init

3.3 pci_direct_init

3.4 上述函数总结

3.5 pci_mmcfg_late_init

4.总结


1.什么是配置空间

        PCI设备有三个相互独立的物理空间地址:存储器地址空间(Memory 地址空间)、I/O地址空间、配置空间地址空间,而配置空间是一个PCI特有的物理空间。系统上电时BIOS检测PCI总线,确定所有连接在PCI连接在PCI总线上的设备以及它们的配置要求,并进行系统配置。所以PCI设备必须实现配置空间,从而实现参数的自动配置。

相关的基本概念可参考如下文章:

【精讲】PCIe基础篇——Memory & IO 地址空间

【精讲】PCIe基础篇——BDF与配置空间

【精讲】PCIe基础篇——BAR配置过程

2.对配置空间的访问

x86架构中pci配置空间的访问有4种方式:pci_bios、pci_conf1、pci_conf2、pci_mmcfg。最优的方式是mmcfg,这需要bios配置,把pci配置空间映射到cpu mem空间;pci_conf1、pci_conf2方式是通过io指针间接访问的;pci_bios方式应该是调用bios提供的服务进程进行访问。使用I/O访问的方式只可以访问配置空间的前256字节,而使用mmcfg的方式则可以完全支持PCIE的扩展寄存器即4K字节的配置空间。在linux初始化的时候,需要给驱动程序选择一种最优的访问方式。

3.PCI驱动中对配置空间的访问的实现

注:基于4.4版本内核
系统在初始化PCI总线的时候,会设置好读取配置空间的方法,读取的方式就上述的两大类(I/O端口访问、MEM访问),提供给上层的可用接口函数是read函数和write函数,系统初始化完成后会将实现好的read方法和write方法绑定至结构体pci_ops,我们首先来看两段代码。

const struct pci_raw_ops *__read_mostly raw_pci_ops;       //pci设备访问
const struct pci_raw_ops *__read_mostly raw_pci_ext_ops;   //pcie设备扩展寄存器访问int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn,int reg, int len, u32 *val)
{//判定是否小于256字节,如果在256范围内则调用raw_pci_ops方法if (domain == 0 && reg < 256 && raw_pci_ops)  return raw_pci_ops->read(domain, bus, devfn, reg, len, val);if (raw_pci_ext_ops)return raw_pci_ext_ops->read(domain, bus, devfn, reg, len, val);return -EINVAL;
}int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn,int reg, int len, u32 val)
{if (domain == 0 && reg < 256 && raw_pci_ops)return raw_pci_ops->write(domain, bus, devfn, reg, len, val);if (raw_pci_ext_ops)return raw_pci_ext_ops->write(domain, bus, devfn, reg, len, val);return -EINVAL;
}static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value)
{return raw_pci_read(pci_domain_nr(bus), bus->number,devfn, where, size, value);
}static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
{return raw_pci_write(pci_domain_nr(bus), bus->number,devfn, where, size, value);
}struct pci_ops pci_root_ops = {.read = pci_read,.write = pci_write,
};

struct pci_raw_ops *raw_pci_ops;static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value)
{//老版本的不会区分,只实现一种方法return raw_pci_ops->read(0, bus->number, devfn, where, size, value);
}static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
{return raw_pci_ops->write(0, bus->number, devfn, where, size, value);
}struct pci_ops pci_root_ops = {.read = pci_read,.write = pci_write,
};

这两段代码都是对read方法和write方法的实现,第一段代码来自于4.4.185版本的内核,第二段代码来自2.6.16版本的内核,我们可以发现在老版本的内核中对于I/O端口访问、MEM访问这两种方式是相互独立的,如果read方法和write方法绑定了pci_conf1和pci_conf2的方式就无法绑定pci_mmcfg的方式。但是在新版本的内核中在这里做出了一个优化,在新版本中我们调用了read方法和write方法时它会去判断你所访问的地址是否属于前256字节,当访问的地址属于前256字节的话调用raw_pci_ops中的读写方法(一般实现为pci_conf1),当访问的地址超过前256字节的话调用raw_pci_ext_ops中的读写方法(一般实现为pci_mmcfg),所以在老版本PCI的初始化时,我们只需填充好raw_pci_ops结构体,并考虑系统究竟使用哪种方法访问配置空间(访问方式唯一),而在新版本的PCI初始化中,我们虽然需要填充好raw_pci_ops和raw_pci_ext_ops两个结构体,但是却实现了两种访问方法的兼容,我们不需要考虑究竟使用那种方式(因为两个都支持)。

新旧内核在PCI读写这里的实现稍有区别,所以我们需要注意,下面我们来看一下PCI配置空间读写函数的实现过程,与PCI读写相关的函数有如下几个:

  • pci_direct_probe
  • pci_mmcfg_early_init
  • pci_direct_init
  • pci_mmcfg_late_init

3.1 pci_direct_probe

int __init pci_direct_probe(void)
{if ((pci_probe & PCI_PROBE_CONF1) == 0)         //此时pci_probe为系统的默认值goto type2;if (!request_region(0xCF8, 8, "PCI conf1"))     //申请i/ogoto type2;if (pci_check_type1()) {raw_pci_ops = &pci_direct_conf1;           //conf1的绑定port_cf9_safe = true;return 1;}release_region(0xCF8, 8);type2:if ((pci_probe & PCI_PROBE_CONF2) == 0)return 0;if (!request_region(0xCF8, 4, "PCI conf2"))return 0;if (!request_region(0xC000, 0x1000, "PCI conf2"))goto fail2;if (pci_check_type2()) {raw_pci_ops = &pci_direct_conf2;port_cf9_safe = true;return 2;}release_region(0xC000, 0x1000);fail2:release_region(0xCF8, 4);return 0;
}

该函数通过pci_probe的值来确定访问方法,该变量的具体数值由内核启动时的传参来确定,值的定义有以下几种:

#define PCI_PROBE_BIOS		0x0001
#define PCI_PROBE_CONF1		0x0002   //I/O访问配置空间
#define PCI_PROBE_CONF2		0x0004  
#define PCI_PROBE_MMCONF	0x0008   //内存访问配置空间

但是从系统启动的grub.cfg可知,bootloder在启动内核时没有传入相应的参数,所以pci_probe使用默认值,即:

unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |PCI_PROBE_MMCONF;

最终该函数将raw_pci_ops结构体绑定为pci_direct_conf1方法,并返回一个类型码1供后续函数使用。

3.2 pci_mmcfg_early_init

void __init pci_mmcfg_early_init(void)
{if (pci_probe & PCI_PROBE_MMCONF) {if (pci_mmcfg_check_hostbridge())    //检查host主桥known_bridge = 1;elseacpi_sfi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);__pci_mmcfg_init(1);      //此函数完成mmcfg的绑定和pci_probe的改变set_apei_filter();}
}

该函数配置了raw_pci_ext_ops方式将其绑定为pci_mmcfg的方式并且同时也重新设置了pci_probe的值,通过添加打印调试信息,我们可以清楚的看到pci_probe前后的变化(对于raw_pci_ext_ops和pci_probe内容操作的函数实际为__pci_mmcfg_init(1)中的pci_mmcfg_arch_init()完成的)。在完成了pci_mmcfg_early_init函数后pci_probe的值变为0x08对PCI_PROBE_MMCONF模式

3.3 pci_direct_init

void __init pci_direct_init(int type)
{if (type == 0)return;printk(KERN_INFO "PCI: Using configuration type %d for base access\n",type);if (type == 1) {raw_pci_ops = &pci_direct_conf1;if (raw_pci_ext_ops)     //在pci_mmcfg_early_init已经完成,所以直接返回return;if (!(pci_probe & PCI_HAS_IO_ECS))return;printk(KERN_INFO "PCI: Using configuration type 1 ""for extended access\n");raw_pci_ext_ops = &pci_direct_conf1;return;}raw_pci_ops = &pci_direct_conf2;
}

该函数根据pci_direct_probe的返回值来对raw_pci_ops和raw_pci_ext_ops进行设置,由于raw_pci_ext_ops在pci_mmcfg_early_init()这个函数中已经设置完毕,所以在此无需进行设置,因此该函数直接返回。

3.4 上述函数总结

以上三个函数都是位于pci_arch_init()函数中,该函数的启动等级为3,此函数就是设置整个PCI总线设备配置空间的读写方法。
函数执行前:
pci_probe = 0xf(默认值)
raw_pci_ops = 空
raw_pci_ext_ops = 空
函数执行后:
pci_probe = 0x8
raw_pci_ops = pci_direct_conf1
raw_pci_ext_ops = pci_mmcfg

3.5 pci_mmcfg_late_init

void __init pci_mmcfg_late_init(void)
{/* MMCONFIG disabled */if ((pci_probe & PCI_PROBE_MMCONF) == 0)return;if (known_bridge)return;/* MMCONFIG hasn't been enabled yet, try again */if (pci_probe & PCI_PROBE_MASK & ~PCI_PROBE_MMCONF) {acpi_sfi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);__pci_mmcfg_init(0);}
}

此函数的执行等级较后,它是pci_mmcfg的第二次配置,即如果前面pci_mmcfg配置异常则再次配置,此时pci_probe的值为0x8而raw_pci_ext_ops绑定pci_mmcfg,所以表示前面的配置成果,所以如果pci_mmcfg_early_init函数完成了配置那么pci_mmcfg_late_init函数一般就直接返回。

4.总结

经过对上面四个函数的的分析,我们可以梳理出对于PCI设备配置空间的访问方法的实现,系统是如何完成的:

至此,在系统对PCi设备进行枚举之前,系统会完成所有对于配置空间访问方法的设置,以保证枚举过程的正常执行,我们需要注意的是在实际使用哪怕是枚举过程我们对于配置空间的访问使用的函数是以下几种:

 pci_read_config_byte(..)     //8pci_read_config_word(..)     //16pci_read_config_dword(..)    //32pci_write_config_byte(..)pci_write_config_word(..)pci_write_config_dword(..)

这些是系统为我们开出的访问函数接口,这些函数本质上是调用pci_ops的两个读写函数的(可以认为是内核在pci_ops的一个封装),我们可以测试以下,分别在pci_read_config_dword和raw_pci_read增加答应信息,我们可以得知pci_read_config_dword最终也就是调用raw_pci_read来完成工作:

本文转自PCI总线驱动代码梳理(二)--配置空间访问的设置

这篇关于Linux源码阅读——PCI总线驱动代码(二)配置空间访问设置的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129165

相关文章

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

mybatis映射器配置小结

《mybatis映射器配置小结》本文详解MyBatis映射器配置,重点讲解字段映射的三种解决方案(别名、自动驼峰映射、resultMap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定... 目录select中字段的映射问题使用SQL语句中的别名功能使用mapUnderscoreToCame

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同