当前位置: 首页 > news >正文

如何编写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_countPF 驱动程序回调函数,用于更改 VF 上的 MSI-X 矢量数量。通过 sysfs“sriov_vf_msix_count”触发。这将更改 VF 消息控制寄存器中的 MSI-X 表大小。
sriov_get_vf_total_msixPF 驱动程序回调以获取可分发到 VF 的 MSI-X 矢量总数。
err_handler
groupssysfs 属性组。
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设备

  1. 按供应商和设备 ID 搜索
struct pci_dev *dev = NULL;
while (dev = pci_get_device(VENDOR_ID, DEVICE_ID, dev))configure_device(dev);
  1. 按类ID搜索
pci_get_class(CLASS_ID, dev)
  1. 按供应商/设备和子系统供应商/设备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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 推荐9个不同风格的音频频谱波形 听音乐怎么能少了它
  • FPGA基础知识
  • 分库分表:应对大数据量挑战的数据库扩展策略
  • Apache Ignite 在处理大规模数据时有哪些优势和局限性?
  • 【第0006页 · 数组】寻找重复数
  • CRIO与Windows下LabVIEW开发对比
  • 数据库的介绍:关系型数据库和非关系型数据库究竟是什么?
  • cmd 常用命令总结
  • 个人网银、手机银行
  • nvidia-smi 随机掉卡,error,禁用GSP功能
  • Day22_K8S
  • 被低估的SQL
  • 〖open-mmlab: MMDetection〗解析文件:configs/_base_/schedules
  • @Value读取properties中文乱码解决方案
  • CTK框架(三): 插件的安装
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • Apache的80端口被占用以及访问时报错403
  • AWS实战 - 利用IAM对S3做访问控制
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • js对象的深浅拷贝
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Object.assign方法不能实现深复制
  • passportjs 源码分析
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • Spark学习笔记之相关记录
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • Vue UI框架库开发介绍
  • 分类模型——Logistics Regression
  • 给初学者:JavaScript 中数组操作注意点
  • 机器学习 vs. 深度学习
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 技术:超级实用的电脑小技巧
  • 使用agvtool更改app version/build
  • 树莓派 - 使用须知
  • 赢得Docker挑战最佳实践
  • 用jquery写贪吃蛇
  • k8s使用glusterfs实现动态持久化存储
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • ​VRRP 虚拟路由冗余协议(华为)
  • $LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (转)jQuery 基础
  • .gitignore文件---让git自动忽略指定文件
  • .NET Framework .NET Core与 .NET 的区别
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .NET处理HTTP请求
  • .NET上SQLite的连接
  • .NET设计模式(11):组合模式(Composite Pattern)
  • @AutoConfigurationPackage的使用
  • [100天算法】-实现 strStr()(day 52)
  • [20180312]进程管理其中的SQL Server进程占用内存远远大于SQL server内部统计出来的内存...
  • [BIZ] - 1.金融交易系统特点