如何编写Linux PCI设备驱动器 之一
如何编写Linux PCI设备驱动器 之一
- PCI寻址
- PCI驱动器使用的API
- pci_register_driver()
- pci_driver结构
- pci_device_id结构
- 如何查找PCI设备
- 存取PCI配置空间
- 读配置空间APIs
- 写配置空间APIs
- where的常量值
- 共用部分
- 类型0
- 类型1
PCI总线通过使用比ISA更高的时钟速率来实现更好的性能;它是时钟运行在 25 或 33 MHz,并且最近还部署了 66 MHz 甚至 133 MHz 实施方案。而且,它配备了32位数据总线,并已扩展了64位包含在规范中。
PCI总线是一种独立于平台的计算机总线,这是 PCI 的一个特别重要的特性。PC 世界一直由处理器特定的接口标准主导。目前 PCI广泛用于不同的平台,比如,X86、ARM、Alpha、PowerPC,以及其他一些平台。
PCI 设备是无跳线的。与大多数较旧的外设不同,它在启动时,自动配置。驱动程序编写者必须关注 PCI自动检测能力。 设备驱动程序必须能够访问设备中的配置信息,以完成初始化。
PCI寻址
每个 PCI 外设由总线号、设备号和功能号来标识。
PCI 规范允许单个系统承载多达 256 条总线,但是由于256总线对于许多大型系统来说是不够的,Linux现在支持PCI域。
- 每个 PCI 域最多可以承载 256 条总线。
- 每个总线最多可容纳 32 人设备。
- 每个设备最多具有8个功能。
所以,每个功能可以通过一个16 位地址进行标识。Linux驱动程序不需要处理这些二进制地址,因为,驱动器是通过使用称为 pci_dev 的特定数据结构,来对设备进行操作。
在单个系统中,通过桥将多个总线连接在一起。桥是一种专用 PCI 外设,它将两个总线连接起来。
PCI系统的总体布局就像一棵树,其中每条总线都连接到上层总线,直至树根处的总线 0。
lspci可以显示 PCI 外设的16位硬件地址。这些存储在 struct pci_dev结构对象中。
PCI设备的sysfs显示就使用了这种寻址方案,但添加PCI 域信息。
当显示硬件地址时,它可以显示为
- 两个值,一个 8 位总线号,一个 8 位设备和功能号。
- 三个值,总线、设备和功能。
- 四个值,域、总线、设备和功能。
使用lspci显示在系统中的PCI设备,lspci显示总线号、设备号和功能号。
~$ lspci | cut -d ":" -f1-2
00:00.0 Host bridge
00:01.0 ISA bridge
00:01.1 IDE interface
00:02.0 VGA compatible controller
00:03.0 Ethernet controller
00:04.0 System peripheral
00:05.0 Multimedia audio controller
00:06.0 USB controller
00:07.0 Bridge
00:0b.0 USB controller
00:0d.0 SATA controller
使用tree显示在系统中的PCI设备,tree显示域号,总线号、设备号和功能号。
~$ tree /sys/bus/pci/devices/
/sys/bus/pci/devices/
├── 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
├── 0000:00:01.0 -> ../../../devices/pci0000:00/0000:00:01.0
├── 0000:00:01.1 -> ../../../devices/pci0000:00/0000:00:01.1
├── 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0
├── 0000:00:03.0 -> ../../../devices/pci0000:00/0000:00:03.0
├── 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
├── 0000:00:05.0 -> ../../../devices/pci0000:00/0000:00:05.0
├── 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0
├── 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
├── 0000:00:0b.0 -> ../../../devices/pci0000:00/0000:00:0b.0
└── 0000:00:0d.0 -> ../../../devices/pci0000:00/0000:00:0d.0
PCI驱动器使用的API
通过 pci_register_driver(),PCI 驱动程序发现系统中的 PCI 设备。当PCI通用代码发现新设备时,将通知具有匹配“描述”的驱动程序。
pci_register_driver() 将大部分设备探测工作留给 PCI 层,并支持设备热插拔。 pci_register_driver() 调用需要传入函数指针表,和驱动程序的高级结构。
一旦驱动程序知道PCI设备,并得到控制权,则,驱动程序通常需要执行以下初始化:
- 启用设备
- 请求MMIO/IOP资源
- 设置 DMA 掩码大小,包括连续和流 DMA
- 分配并初始化共享控制数据, 调用pci_allocate_coherent()函数分配数据空间
- 访问设备配置空间(如果需要)
- 登记IRQ处理程序, 调用request_irq()
- 初始化非 PCI, 即特定芯片部分
- 启用 DMA/处理引擎
pci_register_driver()
PCI 设备驱动程序在初始化期间调用 pci_register_driver(),参数是驱动程序的结构指针:
该函数的词法:
int pci_register_driver(struct pci_driver *drv)
pci_driver结构
struct pci_driver {const char *name;const struct pci_device_id *id_table;int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);void (*remove)(struct pci_dev *dev);int (*suspend)(struct pci_dev *dev, pm_message_t state);int (*resume)(struct pci_dev *dev);void (*shutdown)(struct pci_dev *dev);int (*sriov_configure)(struct pci_dev *dev, int num_vfs);int (*sriov_set_msix_vec_count)(struct pci_dev *vf, int msix_vec_count);u32 (*sriov_get_vf_total_msix)(struct pci_dev *pf);const struct pci_error_handlers *err_handler;const struct attribute_group **groups;const struct attribute_group **dev_groups;struct device_driver driver;struct pci_dynids dynids;bool driver_managed_dma;
};
| 结构成员名称 | 意义 |
|---|---|
| name | 驱动器名称 |
| id_table | 驱动程序支持设备的ID表的指针。大多数驱动程序应使用 MODULE_DEVICE_TABLE (pci,…) 导出此表。 |
| probe | 在对现有设备执行 pci_register_driver() ,或稍后插入新设备时。对于与 ID 表匹配,且尚未被其他驱动程序“拥有”的所有 PCI 设备,将调用此探测函数。对于ID表中的条目与设备匹配的每个设备,此函数都会传递一个“struct pci_dev *”。当驱动程序选择获取设备的“所有权”时,探测函数返回零,否则返回错误代码(负数)。探测函数总是从进程上下文中调用,因此它可以休眠。 |
| remove : | 无论是在驱动程序注销期间,还是手动将其从热插拔插槽中拔出,只要删除该驱动程序, 都会调用remove()函数。删除函数总是从进程上下文中调用,因此它可以休眠。 |
| suspend | 将设备置于低功耗状态 |
| resume | 将设备从低功耗状态唤醒。 |
| shutdown | 挂接到reboot_notifier_list (kernel/sys.c)。目的是停止任何空闲的DMA操作。对于启用LAN唤醒 (NIC) ,或在重新启动之前,更改设备的电源状态非常有用。 |
| sriov_configure | 可选的驱动程序回调函数,借助sysfs “sriov_numvfs”文件, 启用VF 数量的配置 。 |
| sriov_set_msix_vec_count | PF 驱动程序回调函数,用于更改 VF 上的 MSI-X 矢量数量。通过 sysfs“sriov_vf_msix_count”触发。这将更改 VF 消息控制寄存器中的 MSI-X 表大小。 |
| sriov_get_vf_total_msix | PF 驱动程序回调以获取可分发到 VF 的 MSI-X 矢量总数。 |
| err_handler | |
| groups | sysfs 属性组。 |
| dev_groups | 一旦设备绑定到驱动程序,该设备就会创建。dev_groups就是附加到该设备的属性。 |
| driver | 驱动器模型结构 |
| dynids | 动态添加的设备 ID 列表。 |
| driver_managed_dma | 设备驱动程序不使用内核 DMA API 进行 DMA。对于大多数设备驱动程序来说,只要所有 DMA 都是通过内核 DMA API 处理的,就无需关心此标志。对于一些特殊的驱动程序,例如 VFIO 驱动程序,它们知道如何自己管理 DMA 并设置此标志,以便 IOMMU 层允许它们设置和管理自己的 I/O 地址空间。 |
pci_device_id结构
struct pci_device_id {__u32 vendor, device;__u32 subvendor, subdevice;__u32 class, class_mask;kernel_ulong_t driver_data;__u32 override_only;
};
| 结构中成员名称 | 意义 |
|---|---|
| vendor | 供应商ID |
| device | 设备ID |
| subvendor | 子系统供应商ID |
| subdevice | 子系统设备ID |
| class | 设备类、子类和“接口”。大多数驱动程序不需要指定 class/class_mask,因为供应商/设备通常就足够了。 |
| class_mask | 限制比较类字段的哪些子字段。 |
| driver_data | 驱动程序私有的数据。大多数驱动程序不需要使用 driver_data 字段。最佳实践是使用 driver_data 作为等效设备类型的静态列表的索引,而不是将其用作指针。 |
| override_only | 仅当 dev->driver_override 是该驱动程序时才匹配。 |
如何查找PCI设备
- 按供应商和设备 ID 搜索
struct pci_dev *dev = NULL;
while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev))configure_device(dev);
- 按类ID搜索
pci_get_class(CLASS_ID, dev)
- 按供应商/设备和子系统供应商/设备ID进行搜索
pci_get_subsys(VENDOR_ID,DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev)
存取PCI配置空间
读配置空间APIs
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);
写配置空间APIs
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);
where的常量值
linux/pci_regs.h包含所有配置空间位置的常量定义。
共用部分
#define PCI_VENDOR_ID 0x00 /* 16 bits */
#define PCI_DEVICE_ID 0x02 /* 16 bits */
#define PCI_COMMAND 0x04 /* 16 bits */#define PCI_STATUS 0x06 /* 16 bits */
#define PCI_CLASS_REVISION 0x08 /* High 24 bits are class, low 8 revision */
#define PCI_REVISION_ID 0x08 /* Revision ID */#define PCI_CACHE_LINE_SIZE 0x0c /* 8 bits */
#define PCI_LATENCY_TIMER 0x0d /* 8 bits */
#define PCI_HEADER_TYPE 0x0e /* 8 bits */
#define PCI_BIST 0x0f /* 8 bits */#define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */
#define PCI_BASE_ADDRESS_1 0x14 /* 32 bits [htype 0,1 only] */
类型0
/* Header type 0 (normal devices) */
#define PCI_BASE_ADDRESS_2 0x18 /* 32 bits [htype 0 only] */
#define PCI_BASE_ADDRESS_3 0x1c /* 32 bits */
#define PCI_BASE_ADDRESS_4 0x20 /* 32 bits */
#define PCI_BASE_ADDRESS_5 0x24 /* 32 bits */#define PCI_CARDBUS_CIS 0x28
#define PCI_SUBSYSTEM_VENDOR_ID 0x2c
#define PCI_SUBSYSTEM_ID 0x2e
#define PCI_ROM_ADDRESS 0x30 /* Bits 31..11 are address, 10..1 reserved */#define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry *//* 0x35-0x3b are reserved */#define PCI_INTERRUPT_LINE 0x3c /* 8 bits */
#define PCI_INTERRUPT_PIN 0x3d /* 8 bits */
#define PCI_MIN_GNT 0x3e /* 8 bits */
#define PCI_MAX_LAT 0x3f /* 8 bits */
类型1
/* Header type 1 (PCI-to-PCI bridges) */
#define PCI_PRIMARY_BUS 0x18 /* Primary bus number */
#define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */
#define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */
#define PCI_SEC_LATENCY_TIMER 0x1b /* Latency timer for secondary interface */
#define PCI_IO_BASE 0x1c /* I/O range behind the bridge */
#define PCI_IO_LIMIT 0x1d
#define PCI_SEC_STATUS 0x1e /* Secondary status register, only bit 14 used */
#define PCI_MEMORY_BASE 0x20 /* Memory range behind */
#define PCI_MEMORY_LIMIT 0x22
#define PCI_PREF_MEMORY_BASE 0x24 /* Prefetchable memory range behind */
#define PCI_PREF_MEMORY_LIMIT 0x26
#define PCI_PREF_BASE_UPPER32 0x28 /* Upper half of prefetchable memory range */
#define PCI_PREF_LIMIT_UPPER32 0x2c
#define PCI_IO_BASE_UPPER16 0x30 /* Upper half of I/O addresses */
#define PCI_IO_LIMIT_UPPER16 0x32
#define PCI_ROM_ADDRESS1 0x38 /* Same as PCI_ROM_ADDRESS, but for htype 1 */
#define PCI_BRIDGE_CONTROL 0x3e相关文章:
如何编写Linux PCI设备驱动器 之一
如何编写Linux PCI设备驱动器 之一 PCI寻址PCI驱动器使用的APIpci_register_driver()pci_driver结构pci_device_id结构 如何查找PCI设备存取PCI配置空间读配置空间APIs写配置空间APIswhere的常量值共用部分类型0类型1 PCI总线通过使用比ISA更高的时钟速率来实现更好的性能&…...
梯度弥散问题及解决方法
梯度弥散问题及解决方法 简要阐述梯度弥散发生的原因以及现象针对不同发生原因有什么解决方案1. 使用ReLU及其变体激活函数2. 权重初始化3. 批量归一化(Batch Normalization)4. 残差连接(Residual Connections)5. 梯度裁剪(Gradient Clipping)简要阐述梯度弥散发生的原因…...
Python中pickle文件操作及案例-学习篇
一、简介 Pickle 算是Python的一种数据序列化方法,它能够将对象转换为字节流,进而可以保存到文件中或通过网络传输给其他Python程序。这种方式非常适合快速简便地保存复杂的数据结构,例如列表、字典、自定义对象等。 二、pickle文件的读写 …...
微服务日常总结
1.当我们在开发中,需要连接多个库时,可以在yml中进行配置。 当在查询的时候,跨库时,需要通过DS 注解来指定,需要yml配置需要保持一致。 2. 当我们想把数据存入到clob类型中,需要再字段 的占位符后面加上j…...
C和C++内存管理
C和C内存管理 (一)C/C内存分布(二)C语言动态内存管理(三)c内存管理(3.1)new/delete操作内置类型(3.2)new和delete操作自定义类型 (四)…...
axios取消请求
1.使用CancelToken: class RequestHttp {service: AxiosInstance;public constructor(config: AxiosRequestConfig) {// 实例化axiosthis.service axios.create(config);/*** description 请求拦截器* 客户端发送请求 -> [请求拦截器] -> 服务器*/this.service.interce…...
阿里中间件——diamond
一、前言 最近工作不忙闲来无事,仔细分析了公司整个项目架构,发现用到了很多阿里巴巴集团开源的框架,今天要介绍的是中间件diamond. 二、diamond学习笔记 1、diamond简介 diamond是一个管理持久配置(持久配置是指配置数据会持久化…...
pyenv -- 一款macos下开源的多版本python环境安装管理工具 国内加速版安装 + 项目venv虚拟环境 pip加速 使用与总结
一个比较方便实用的python多版本环境安装管理工具, 阿里云加速版本 pyenv安装方法: 直接克隆本下面到你的本地目录,然后设置环境变量即可 git clone https://gitee.com/tekintian/pyenv.git ~/.pyenv 环境变量配置 在~/.bash_profile 或者 .zshrc 中增加环境变量 export …...
VitePress 自定义 CSS 指南
VitePress 是一款基于 Vite 和 Vue 3 的静态网站生成器,专为文档编写而设计。尽管 VitePress 提供了丰富的默认主题,但在某些情况下,我们可能需要对其进行更深入的定制以满足特定的视觉需求。本文将详细介绍如何通过覆盖根级别的 CSS 变量来自…...
【舍入,取整,取小数,取余数丨Excel 函数】
数学函数 1、Round函数 Roundup函数 Rounddown函数 取整:(Int /Trunc)其他舍入函数: 2、Mod函数用Mod函数提取小数用Mod函数 分奇偶通过身份证号码判断性别 1、Round函数 Roundup函数 Rounddown函数 Round(数字,保留几位小数)(四…...
无线信道中ph和ph^2的场景
使用 p h ph ph的情况: Rayleigh 分布的随机变量可以通过两个独立且相同分布的零均值、高斯分布的随机变量表示。设两个高斯随机变量为 X ∼ N ( 0 , σ 2 ) X \sim \mathcal{N}(0, \sigma^2) X∼N(0,σ2)和 Y ∼ N ( 0 , σ 2 ) Y \sim \mathcal{N}(0, \sigma^2)…...
HCIA--实验五:静态路由综合实验
静态路由综合实验 一、实验内容: 1.需求/目的: 在ensp模拟器中使用四个路由器,并且在路由器上创建loopback接口,相当于连接了一台主机,通过配置静态路由的方式实现全网通。 二、实验过程 1.道具: 4个…...
不同vlan之间的通信方法
1.通过路由器的物理接口 1.给PC1,PC2配置IP地址,网关2.进入交换机配置vlan,交换机所有口都配置access口并绑定vlan3.配置路由器,进入路由器的两个接口配置网关IP和掩码缺点:成本高,每增加一个vlan就需要一个物理端口和…...
java后端框架
框架就是对技术的封装。 本篇博客小博主首先对以后我们要学习的框架进行简单概述,使大家对框架有一定的基本概念。 一.mybatis mybatis就是对jdbc(数据库连接)进行封装,避免了jdbc中手动设置参数,手动映射结果的操作。…...
如何在Word中插入复选框
如何在Word中插入复选框:详细教程与技巧 在Word中插入复选框是一项非常实用的技巧,尤其是在制作问卷调查、待办事项清单、交互式表单或文档中需要用户进行选择时,复选框不仅能提高文档的功能性,还能显得更加专业。本文将详细讲解…...
Android 源码中jni项目 加载so目录小结
Android 源码中jni项目 加载so目录小结 文章目录 Android 源码中jni项目 加载so目录小结一、前言二、so目录验证测试1、jni so文件错误报错(1)报错1 - 未找到so文件:(2)报错2 - so文件中未找到native方法: …...
24/9/6算法笔记 kaggle 房屋价格
预测模型主要分为两大类: 回归模型:当你的目标变量是连续的数值时,你会使用回归模型进行预测。回归模型试图找到输入特征和连续输出之间的关联。一些常见的回归模型包括: 线性回归(Linear Regression)岭回归…...
【MA35D1】buildroot 编译使用经验
文章目录 芯片介绍Buildroot开发Linux实践环境搭建代码获取编译执行步骤(仅适用于我公司产品) 后续有需要更改的输出文件目录 芯片介绍 NuMicro MA35D1系列为一颗异核同构的多核心微处理器,适用于高端 Edge IIoT Gateway。它是基于双核 64 位…...
排查 MyBatis XML 配置中的 IF 语句与传值名称不匹配的 Bug
文章目录 本文档只是为了留档方便以后工作运维,或者给同事分享文档内容比较简陋命令也不是特别全,不适合小白观看,如有不懂可以私信,上班期间都是在得 前言,在改一个bug得时候发现一个有意思得问题,就是myb…...
数字证书与公钥基础设施
关注这个证书的其他相关笔记:NISP 一级 —— 考证笔记合集-CSDN博客 0x01:数字证书 数字证书是由第三方可信机构(一般是证书服务器)颁发的数字证书,可以证明身份的可信度。 数字证书具有以下特点以及性质:…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
