本文主要是介绍如何编写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设备驱动器 之一的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!