如何编写Linux PCI设备驱动器 之一

2024-09-07 10:12

本文主要是介绍如何编写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

这篇关于如何编写Linux PCI设备驱动器 之一的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决