7.PCIE配置空间读写软件_pcie 寄存器 读写-程序员宅基地

技术标签: pci-e  celestica  PCIe  

软件-7.PCIE配置空间读写

软件-7.PCIE配置空间读写

  • 软件-7.PCIE配置空间读写
    • 软件读写配置空间
      • 驱动层接口
      • 原理分析
        • 驱动层代码接口
        • 驱动层接口与原理
          • 相关参考
          • 基础知识
          • raw_pci_ops 得初始化
          • raw_pci_ext_ops 得原理-ECAM
          • 如何获取ECAM得基地址?

软件读写配置空间

驱动层接口

inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val); 
inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val); 
inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val); 
inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val); 
inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val); 
inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

根本就是调用 pci总线接口

int pci_bus_read_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 *val);  //读字节
int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16 *val);  //读字
int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32 *val); //读双字
int pci_bus_write_config_byte (struct pci_bus *bus, unsigned int devfn, int where, u8 val);  //写字节
int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int where, u16  val); //写字
int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int where, u32  val); //写双字

在drivers\pci\access.c 中

int pci_bus_write_config_##size \
	(struct pci_bus *bus, unsigned int devfn, int pos, type value)	\
{									\
	int res;							\
	unsigned long flags;						\
	if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;	\
	pci_lock_config(flags);						\
	res = bus->ops->write(bus, devfn, pos, len, value);		\                       // 重点
	pci_unlock_config(flags);					\
	return res;							\
}

所以主要代码是 : bus->ops

原理分析

驱动层代码接口

根总线读写操作接口初始化

pci_acpi_scan_root
    ->acpi_pci_root_create  // 重要参数: acpi_pci_root_ops
        ->pci_create_root_bus // bridge->ops=acpi_pci_root_ops.pci_ops, 也就是 pci_root_ops
        ->pci_register_host_bridge
            ->bus->ops = bridge->ops // acpi_pci_root_ops.ops, 也就是 pci_root_ops

所以bus得操作接口为 acpi_pci_root_ops

struct pci_ops pci_root_ops = {
	.read = pci_read,
	.write = pci_write,
};
static struct acpi_pci_root_ops acpi_pci_root_ops = {
	.pci_ops = &pci_root_ops,
};

所以只需要分析 pci_root_ops即可

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;
}

所以可以得到结论:
pci读写 0x00-0xFF 使用raw_pci_ops
pci读写 0x100-0xFFF 使用 raw_pci_ext_ops

驱动层接口与原理

相关参考

基础知识

在X86架构上有关于这部分的描述:

10th Generation Intel Core Processors, Datasheet Volume 1 of 2 中 P29页描述:

 

PCI Express 将配置空间扩展到每个设备/功能4K字节
PCI Express 配置空间分为 一个PCI兼容区域(就是前256个字节组成)和 一个扩展的PCIExpress 区域(就是 0x100-0xFFF)。

PCI前256字节配置空间:可以通过 PCI规范中定义的机制(就是 通过 0cf8-0cff : PCI conf1 两个ioport通过BDF来寻址访问 ) 或 使用PCI Express 增强配置机制(ECAM- PCI Express Enhanced Configuration Access Mechanism)访问机制来访问PCI兼容区域

PCI的0x100-0xFFF的ECAM访问,使用ioremap去访问PCI Express区域,这个属于硬件支持,基地址从ACPI来获取到
PCI Express 主机桥,将内存映射的PCI Express 配置空间访问从主机处理器转换为PCI Express 配置周期。为了保持与PCI配置寻址机制的兼容性,建议系统软件仅使用32位操作(32位对齐)访问增强的配置空间。有关PCI兼容和PCI Express 增强配置机制和事务规则的详细信息,请参阅《 PCI Express基本规范》。

raw_pci_ops 得初始化

初始化入口

// init.c:45:arch_initcall(pci_arch_init);
pci_arch_init
    // 《Linux那些事之PCI》P5中描述了三种PCI access mode,  
    //  内核中CONFIG_PCI_DIRECT这个宏有配直接Direct去访问
    pci_direct_probe();   // 初始化0xCF8和0xCFC,并初始化

    if (x86_init.pci.arch_init && !x86_init.pci.arch_init())  // 函数没实现,哈哈
        return 0;

    // pci_pcbios_init();  // CONFIG_PCI_BIOS--不配置,不用看
    pci_direct_init(type); // raw_pci_ops = raw_pci_ext_ops 预留读写pci配置空间的接口
pci_probe & PCI_PROBE_CONF1 # 判断,什么是CONF1和CONF2
request_region(0xCF8, 8, "PCI conf1") # 为什么使用0xCF8
pci_check_type1()	# 检测type1
raw_pci_ops = &pci_direct_conf1;  # !!! 初始化pci配置空间操作接口

结论(重点):
所以raw_pci_ops = &pci_direct_conf1; 可以看到 使用0xCF8和0xCFC访问PCI基础配置空间

inno@DEV-005:~$ sudo cat /proc/ioports  | grep "PCI conf"   # 申请到得IO接口
0cf8-0cff : PCI conf1

// arch/x86/direct.c中                                                     // 访问格式
#define PCI_CONF1_ADDRESS(bus, devfn, reg) \
	(0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
	| (devfn << 8) | (reg & 0xFC))
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);  // 配置地址
u32 value = inl(0xCFC); // 读取配置

深入PCI与PCIe之二:软件篇 中描述了CONFIG_ADD_RESS得结构:

CONFIG_ADDRESS寄存器格式:
31 位:Enabled位。
23:16 位:总线编号。 // bus
15:11 位:设备编号。 // devfn[7:3]
10: 8 位:功能编号。 // devfn[2:0]
7: 2 位:配置空间寄存器编号。 // 配置空间偏移地址, 注:因为是32位端口,所以4字节访问。
1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。

raw_pci_ext_ops 得原理-ECAM

参考:

ECAM是访问PCIe配置空间一种机制,PCIe配置空间大小是4k
4kbyte寄存器地址空间,需要12bit bit 0~bit11
Function Number bit 12~bit 14
Device Number bit 15~bit 19
Bus Number bit 20~bit 27

如何访问一个PCIe设备的配置空间呢?
比如ECAM 基地址是0xd0000000

devmem 0xd0000000就是访问00:00.0 设备偏移0寄存器,就是Device ID和Vendor ID
devmem 0xd0100000就是访问01:00.0 设备偏移0寄存器

所以,这里重点就是看ECAM得初始化

pci_acpi_scan_root      // 主桥信息struct pci_root_info  和  struct pci_sysdata 初始化
    -> acpi_pci_root_create // ECAM初始化,主桥资源初始化
        ->  ops->init_info(info)  // 就是 acpi_pci_root_ops得 pci_acpi_root_init_info
        
pci_acpi_root_init_info
    -> setup_mcfg_map

setup_mcfg_map

pci_mmcfg_late_init();
	 // #define ACPI_SIG_MCFG           "MCFG"	/* PCI Memory Mapped Configuration table */
	acpi_table_parse(ACPI_SIG_MCFG, pci_mcfg_parse);  // "ACPI中关于MCFG的描述"
    
if (raw_pci_ext_ops == NULL)
			raw_pci_ext_ops = &pci_mmcfg;       // !!! 初始化接口

如何获取ECAM得基地址?

方式一:打印ACPI表

sudo apt-get install -y iasl acpica-tools
mkdir -p  testacpi  && cd testacpi
acpidump > acpidump.out     # 将ACPI表二进制打印到文件
acpixtract -a acpidump.out  # 解析acpi表,生成各个dat文件
iasl -d mcfg.dat            # iasl会解析acpi 二进制表,生成xxx.dsl描述文件
cat mcfg.dsl 					# 可以查看mcfg的配置文件

比如Intel,我这里看到的是 0x8000_0000,
Start BusNum=00, End BusNum=ff, 所以所有总线的ECAM都在这个空间,按照ECAM地址空间依次偏移即可。

cat /proc/ioport | grep 80000000
80000000-8FFFFFFF  PCI MMCONFIG [bus 00 - ff]

方式二:看内核启动打印

比如这个ECAM的基地址是0xe0000000
[    0.111732] PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0xe0000000-0xefffffff] (base 0xe0000000)
[    0.111734] PCI: MMCONFIG at [mem 0xe0000000-0xefffffff] reserved in E820

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/star871016/article/details/119775483

智能推荐

windows将frp命令开机自启系统服务_-services.msc-frpc-_wyy7293的博客-程序员宅基地

设置开机启动和后台运行同样的道理,我们也需要对客户端设置后台运行和开机自启。借助 winsw 工具可以将frpc注册为windows系统中的服务。下载winsw最新版,为了方便将其重命名为winsw.exe, 将该文件和frpc.exe放在一起,然后新建winsw.xml写入以下内容:<service> <id>frp</id> <name>frp</name> <description>用frp发布本地电_-services.msc-frpc-

基于内容的图像检索_python+flask制作图像检索系统_sunny*&*的博客-程序员宅基地

图像搜索引擎一般有三种实现方式:(1)Search By Metadata,这种方式不会考虑图片本身内容(图片包含物体,以及图像像素分布等),纯粹根据图像标签来进行检索。如果某个网页中有一张赛马的图片,并且网页文本内容中包含“赛马”(或者相关词汇)的文字,当用户搜索“赛马”、“马”、“horse”等关键字时,搜索引擎就会把这张图当作检索结果返回给用户。换句话说,此时的图像搜索引擎干的事情跟普通搜索引擎差不多,匹配关键词,并将对应图片返回给用户。这种工作方式的优点是速度快,在普通搜索引擎的技术基础之上很容._python+flask制作图像检索系统

Linux安装kettle_kettle在linux安装使用-程序员宅基地

1、上传下载好的安装包2、创建kettle和组和用户,并使kettle用户属于kettle组,给kettle设置密码[root@localhost ~]# groupadd kettle[root@localhost ~]# useradd -r -g kettle kettle[root@localhost ~]# passwd kettle3、解压上传好的kettle包[root@localhost ~]# unzip pdi-ce-7.1.0.0-12.zip4、授权1)将这个文_kettle在linux安装使用

信安渗透实验四-程序员宅基地

3. 实验过程1:先用指令“ifconfig”虚拟机ip,我的是192.168.85.128再用指令“nmap 192.168.85.1-255”寻找出靶机ip:192.168.85.1332.可以看到端口开放有:端口22,22端口是ssh服务,端口22可以实现远程连接端口80,80端口是HTTP服务,80是为http协议开放的上图可以查询到靶机IP,用火狐浏览器访问刚开始点开觉得没啥有用信息,然后看到下面有“log in”点进去是需要账号密码登入的.._信安渗透

vue elementUI el-select 同时获取label 和 value 的值_vue select 获取选中的子级value-程序员宅基地

<template><div><el-select v-model=“fruit” @change=“handleChange”><el-option v-for=“item in selectList” :key=“item.whsCode” :label=“item.fruitName” :value=“item.fruitEnName”></el-option></el-select></div></_vue select 获取选中的子级value

QFramework (v0.1.x)入门学习(二) 下载与版本介绍_qframework v1.0下载-程序员宅基地

该博客只是自己记录学习所用,建议大家观看凉鞋大神的教程http://qf.liangxiegame.com/qf/communityusing System.Collections;using System.Collections.Generic;using UnityEngine;namespace QF.Guide{ public class HelloQF..._qframework v1.0下载

随便推点

Java程序员应该掌握的一些技术-程序员宅基地

Java 基础部分:OOP 概念抽象类与接口构造函数与 initialization order(初始化顺序)Java 中的一些关键字:static、final、 volatile、synchronized、transient、this 等等File I/O 和序列化Collections:List、Map、Set异常泛型JVM 和内存管理多线程和同

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(11)-验证码实现和底层修改-程序员宅基地

ASP.NET MVC+EF框架+EasyUI实现权限管理系列(11)-验证码实现和底层修改 原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(11)-验证码实现和底层修改   ASP.NET MVC+EF框架+EasyUI实现权限管系列    (开篇)(1):框架搭建(2):数据库访问层的设计Dem..._easyui设置验证码

【Python】python多进程,函数内print的内容没有打印出来_python进程池中print无效-程序员宅基地

问题:python多进程,子函数内容没有打印出来。Simple Python Multiprocessing function doesn't output results I have this very simple function right here in which I'm trying to run and test on, however, it doesn't out..._python进程池中print无效

世界编程语言排行榜2008年06月_编程语言流行度排名 2008年-程序员宅基地

世界编程语言排行榜08年06月 TIOBE Programming Community Index for April 2008 注: TIOBE 世界编程语言排行榜展现了编程语言的流行趋势。每个月,都有最新的数据被更新。这份排行榜的数据取样来源于互联网上富有经验的程序员、商业应用、著名的搜 索引擎(诸如谷歌、 MSN 、雅虎)的关键字排名、 Alexa 上的排名等。请注意这个排行榜只是反映了某个_编程语言流行度排名 2008年

DirectShow音频采集pcm,实时编码AAC,附源码-程序员宅基地

定期送福利,今天给大家送上Windows中利用DirectShow采集microphone音频,并将采集到的pcm数据,利用FAAC库编码成AAC,进行本地存储或者网络传输。直接贴代码,解析看注释:/* 功能描述: 简单实现用DirectShow采集Windows麦克风音频PCM数据 经过FAAC编码成AAC存储文件,可根据项目修改成直播,对讲 等实时..._dsaudio 音频采集