PCI设备驱动
2.PCI规范能够实现32位并行数据传输,工作频率为33MHZ或66MHZ,最大吞吐率为266MB/s。PCI-E是目前PCI系列最具代表性的一种,与并行PCI总线区别在于它采用串行的传输模式,最大支持32个串行连接。在数字图形、图像等处理,以及高速实时数据采集与处理等对数据传输率要求高的应用中,采用PCI总线进行数据传输。比如PCIe64位总线,133MHZ,吞吐率1GB/s,内存于台式机和服务
一、PCI基础
1.PCI总线为高性能局部总线,主要解决外部设备之间以及外部设备与主机之间告诉数据传输。在数字图形、图像等处理,以及高速实时数据采集与处理等对数据传输率要求高的应用中,采用PCI总线进行数据传输。
2.PCI规范能够实现32位并行数据传输,工作频率为33MHZ或66MHZ,最大吞吐率为266MB/s。PCI-E是目前PCI系列最具代表性的一种,与并行PCI总线区别在于它采用串行的传输模式,最大支持32个串行连接。每个传输方向上吞吐率为250MB/s,方向上总传输速率达8GB/s。
3.串行通信比并行通信速度快成本低。比如PCIe64位总线,133MHZ,吞吐率1GB/s,内存于台式机和服务器,比PCI宽,但能插入PCI卡。
4.PCI总线特点
(1)具有隐含的中央仲裁系统。
(2)具有与处理器和存储器子系统完全并行的操控能力。
(3)提供地址和数据的奇偶校验,完全的多总线主控能力。
二、PCI数据结构
1.连接cpu以及PCI系统对应的数据结构类型pci_host_bridge:
2.描述PCI总线对应的数据结构pci_bus:
3.用于指向PCI读写操作函数集的结构体pci_ops:
4.专门用于描述总线的物理插槽结构体pci_slot:
5.PCI设备结构体pci_dev:
6.PCI驱动程序结构体pci_driver:
7.PCI卡标识结构体pci_device_i:
8.结构体之间的关系
三、示例代码
/*
下面的代码用于实现一个PCI,设备驱动程序。其主要功能是识别特定的PCI设备,
对这些设备进行初始化、配置和管理,处理设备终端,并在设备移除时进行相关清理工作
*/
#include<linux/module.h>
#include<linux/pci.h> //包含了与 PCI 设备驱动相关的定义和函数,用于处理 PCI 设备的识别、配置和管理
#include<linux/types.h>
//用户自定义的结构体类型,作用于中断服务函数里面
struct pci_Card{
//这是一个表示端口读写变量的成员,它通常存储的是 PCI 设备所使用的 I/O 端口的基地址。通过这个地址,驱动程序可以对设备进行读写操作,实现与设备的数据交互
resource_size_t io;
long range,flags; //IO地址范围,标志位
void __iomem *ioaddr; //IO映射地址
//表示 PCI 设备使用的中断号。当设备需要向 CPU 发出中断请求时,会使用这个中断号,驱动程序可以通过注册中断服务函数来处理这些中断
int irq;
};
//驱动程序支持的设备列表,如果有匹配的设备,那么改驱动程序就会被执行
static struct pci_device_id ids[] =
{
{
PCI_DEVICE(PCI_VENDOR_ID_INTEL, /* vendor id, 厂家ID */
0x100f) /* device id, 设备ID,可以通过lspci或者到相应的目录里面看 */
},
{
PCI_DEVICE(PCI_VENDOR_ID_INTEL,
PCI_DEVICE_ID_INTEL_80332_0)
},
{0,} /*最后一组为0,表示结束*/
};
/* 进行注册,PCI总线,ids为上面定义的设置 */
//MODULE_DEVICE_TABLE:是一个宏,用于将设备 ID 表注册到内核中,告知内核该驱动程序支持哪些 PCI 设备
MODULE_DEVICE_TABLE(pci, ids);
/* 打印配置空间里面的一些数据信息 */
void skel_get_configs(struct pci_dev *dev)
{
uint8_t revisionId;
uint16_t vendorId, deviceId;
uint32_t classId;
/*
从参数dev里面也是可以打印vendorID等信息的,
这个结构体里面包含这个成员变量,用下面的API
获取得到的结果也是一致的
*/
pci_read_config_word(dev, PCI_VENDOR_ID, &vendorId);
printk("vendorID = %x", vendorId);
pci_read_config_word(dev, PCI_DEVICE_ID, &deviceId);
printk("deviceID = %x", deviceId);
pci_read_config_byte(dev, PCI_REVISION_ID, &revisionId);
printk("revisionID = %x",revisionId);
pci_read_config_dword(dev, PCI_CLASS_REVISION, &classId);
printk("classID = %x",classId);
}
//设备中断服务函数的实现
static irqreturn_t pci_Mcard_interrupt(int irq,void *dev_id){
//是设备的私有数据指针,这里将其转换为 struct pci_Card 类型的指针
struct pci_Card *pci_Mcard = (struct pci_Card *)dev_id;
//中断函数里面打印中断号
printk("irq = %d, pci_Mcard_irq = %d\n", irq, pci_Mcard->irq);
//表示中断已被处理
return IRQ_HANDLED;
}
/*
probe 函数是一个 PCI 驱动的探测函数,当系统检测到与驱动匹配的 PCI 设备时,会调用该函数进行设备的初始化和资源分配。函数主要完成以下几个任务:
使能 PCI 设备。
为存储设备信息的结构体分配内存。
获取设备的中断号并检查其有效性。
获取设备的 I/O 内存相关信息。
请求使用设备的资源区域。
将设备的物理地址映射到内核虚拟地址空间。
申请中断并设置中断服务子函数。
将设备信息与驱动关联起来。
获取设备的配置信息。
*/
/* 有匹配的设备,这个函数会执行 */
static int probe(struct pci_dev *dev, const struct pci_device_id *id)
{
// 初始化返回值变量,用于记录函数执行过程中的状态
int retval = 0;
// 定义一个指向自定义结构体 pci_Card 的指针,用于存储 PCI 设备相关信息
struct pci_Card *pci_Mcard;
// 打印信息,表明进入了 probe 函数
printk("probe func\n");
/* 设备使能 */
// 调用 pci_enable_device 函数使能 PCI 设备
/*
功能:该函数用于使能指定的 PCI 设备。在对 PCI 设备进行操作之前,通常需要先使能它,这样设备才能正常工作。
使能操作会设置设备的配置寄存器,开启设备的电源、时钟等必要条件,同时还会请求设备所需的基本资源(如 I/O 端口、内存空间等)。
*/
if(pci_enable_device(dev)) {
// 如果使能失败,打印错误信息
printk (KERN_ERR "IO Error.\n");
// 返回错误码 -EIO 表示输入输出错误
return -EIO;
}
// 为 pci_Mcard 结构体分配内存,使用 GFP_KERNEL 标志表示在正常内核内存分配模式下分配
pci_Mcard = kmalloc(sizeof(struct pci_Card), GFP_KERNEL);
// 检查内存分配是否成功
if(!pci_Mcard) {
// 如果分配失败,打印错误信息,__func__ 是一个预定义宏,代表当前函数名
printk("In %s,kmalloc err!", __func__);
// 返回错误码 -ENOMEM 表示内存不足
return -ENOMEM;
}
/* 设备中断号 */
// 将 PCI 设备的中断号赋值给 pci_Mcard 结构体的 irq 成员
pci_Mcard->irq = dev->irq;
// 检查中断号是否有效
if(pci_Mcard->irq < 0) {
// 如果中断号无效,打印错误信息
printk("IRQ is %d, it's invalid!\n", pci_Mcard->irq);
// 跳转到 out_pci_Mcard 标签处进行错误处理
goto out_pci_Mcard;
}
/*获取 io 内存相关信息*/
// 获取 PCI 设备第 0 个 BAR(Base Address Register)的起始地址,并赋值给 pci_Mcard 结构体的 io 成员
/*获取指定 PCI 设备的某个 BAR(Base Address Register,基地址寄存器)所对应的资源起始地址。
每个 PCI 设备通常有多个 BAR,用于标识设备的不同资源区域(如 I/O 端口范围、内存映射区域等)。*/
pci_Mcard->io = pci_resource_start(dev, 0);
// 计算第 0 个 BAR 的地址范围,并赋值给 pci_Mcard 结构体的 range 成员
pci_Mcard->range = pci_resource_end(dev, 0) - pci_Mcard->io + 1;
// 获取第 0 个 BAR 的资源标志,并赋值给 pci_Mcard 结构体的 flags 成员
pci_Mcard->flags = pci_resource_flags(dev, 0);
// 打印第 0 个 BAR 的起始地址、范围和标志信息
printk("start %llx %lx %lx\n", pci_Mcard->io, pci_Mcard->range, pci_Mcard->flags);
// 根据 flags 标志判断第 0 个 BAR 是内存类型还是端口类型,并打印相应信息
printk("PCI base addr 0 is io%s.\n", (pci_Mcard->flags & IORESOURCE_MEM)? "mem" : "port");
/*防止地址访问冲突,所以这里先申请*/
// 调用 pci_request_regions 函数请求使用 PCI 设备的资源区域,名称为 "pci_module"
/*
功能:该函数用于请求使用指定 PCI 设备的所有资源区域(如 I/O 端口、内存区域等)。在使用这些资源之前,需要先向系统请求使用权限,以避免资源冲突。
参数:
dev:指向 struct pci_dev 结构体的指针,表示要请求资源的 PCI 设备。
res_name:一个字符串,用于标识这些资源的名称,通常用于调试和日志记录。
*/
retval = pci_request_regions(dev, "pci_module");
// 检查资源请求是否成功
if(retval) {
// 如果请求失败,打印错误信息
printk("PCI request regions err!\n");
// 跳转到 out_pci_Mcard 标签处进行错误处理
goto out_pci_Mcard;
}
/*再进行映射*/
// 调用 pci_ioremap_bar 函数将第 0 个 BAR 的物理地址映射到内核虚拟地址空间,并赋值给 pci_Mcard 结构体的 ioaddr 成员
/*
功能:该函数用于将指定 PCI 设备的某个 BAR 所对应的物理地址映射到内核的虚拟地址空间。
在 Linux 内核中,不能直接访问物理地址,需要通过虚拟地址来间接访问。
pci_ioremap_bar 函数会为指定的物理地址分配一块虚拟地址空间,并建立映射关系,
这样驱动程序就可以通过操作虚拟地址来访问 PCI 设备的硬件寄存器或内存区域。
参数:
dev:指向 struct pci_dev 结构体的指针,表示要操作的 PCI 设备。
bar:指定要映射的 BAR 编号。
*/
pci_Mcard->ioaddr = pci_ioremap_bar(dev, 0);
// 检查映射是否成功
if(!pci_Mcard->ioaddr) {
// 如果映射失败,打印错误信息
printk("ioremap err!\n");
// 设置返回值为 -ENOMEM 表示内存不足
retval = -ENOMEM;
// 跳转到 out_regions 标签处进行错误处理
goto out_regions;
}
/*申请中断 IRQ 并设定中断服务子函数*/
// 调用 request_irq 函数申请中断,指定中断号、中断服务子函数 pci_Mcard_interrupt、中断标志 IRQF_SHARED(表示允许共享中断)、中断名称 "pci_module" 和上下文数据 pci_Mcard
/*
功能:该函数用于向系统请求一个中断号,并注册一个中断处理程序。当指定的中断号对应的中断事件发生时,系统会调用注册的中断处理程序来处理该事件。
参数:
irq:要请求的中断号。
handler:中断处理程序的函数指针,其原型为 irqreturn_t (*irq_handler_t)(int, void *)。
flags:中断标志,用于指定中断的触发方式(如上升沿触发、下降沿触发等)、是否允许共享中断等属性。常见的标志有 IRQF_SHARED(允许共享中断)、IRQF_TRIGGER_RISING(上升沿触发)等。
name:一个字符串,用于标识该中断的名称,通常用于调试和日志记录。
dev:传递给中断处理程序的上下文数据指针,在中断处理程序中可以通过这个指针访问相关的数据。
*/
retval = request_irq(pci_Mcard->irq, pci_Mcard_interrupt, IRQF_SHARED, "pci_module", pci_Mcard);
// 检查中断申请是否成功
if(retval) {
// 如果申请失败,打印错误信息
printk (KERN_ERR "Can't get assigned IRQ %d.\n", pci_Mcard->irq);
// 跳转到 out_iounmap 标签处进行错误处理
goto out_iounmap;
}
// 将 pci_Mcard 结构体指针作为驱动数据与 PCI 设备关联起来
pci_set_drvdata(dev, pci_Mcard);
// 调用 skel_get_configs 函数获取 PCI 设备的配置信息
skel_get_configs(dev);
// 函数执行成功,返回 0
return 0;
out_iounmap:
// 解除之前通过 pci_ioremap_bar 建立的映射
iounmap(pci_Mcard->ioaddr);
out_regions:
// 释放之前通过 pci_request_regions 申请的资源区域
pci_release_regions(dev);
out_pci_Mcard:
// 释放之前为 pci_Mcard 结构体分配的内存
kfree(pci_Mcard);
// 返回错误码
return retval;
}
//移除PCI设备
static void remove(struct pci_dev *dev){
//从指定的 PCI 设备对象(由 dev 指针代表)中获取之前为该设备关联的私有数据
struct pci_Card *pci_Mcard = pci_get_drvdata(dev);
//释放之前为PCI设备注册的中断处理程序
free_irq(pci_Mcard->irq,pci_Mcard);
// 解除之前映射的PCI设备的I/O地址空间
iounmap(pci_Mcard->ioaddr);
// 释放PCI设备占用的资源区域
pci_release_regions(dev);
// 释放之前为pci_Mcard结构体分配的内存
kfree(pci_Mcard);
// 禁用PCI设备
pci_disable_device(dev);
// 打印信息,表示PCI设备移除成功
printk("remove pci device ok\n");
}
/* 结构体成员变量填充 */
//struct pci_driver pci_driver:定义了一个 PCI 驱动结构体,包含驱动的名称、支持的设备列表、探测函数和移除函数。
static struct pci_driver pci_driver =
{
.name = "pci_module",
.id_table = ids,
.probe = probe,
.remove = remove,
};
/* 模块入口函数 */
static int __init pci_module_init(void)
{
printk("PCI module entry function\n");
/* 而pci_register_driver()就是把某个设备下的driver程序放入struct kset drivers结构体中,
而这个过程又分成如下子过程:
首先会调用int _pci_register_driver()函数,在这个函数里面,
首先会把pci_driver存储的信息复制到device_driver中,
因为内核最终要注册的是一个device的driver信息,
而不单单指pci设备的driver信息。在复制完成之后,
内核会调用driver_register()函数注册device_driver的信息,*/
return pci_register_driver(&pci_driver);
}
/* 模块退出函数 */
static void __exit pci_module_exit(void)
{
printk("PCI module exit function.\n");
// PCI驱动被卸载时,需要调用pci_unregister_driver
pci_unregister_driver(&pci_driver);
}
MODULE_LICENSE("GPL");
module_init(pci_module_init);
module_exit(pci_module_exit);
/*
触发驱动程序调用的硬件操作
1. 设备插入或开机检测
PCI 设备插入:如果你的系统支持热插拔 PCI 设备,当你将支持的 PCI 设备插入到系统的 PCI 插槽中时,内核会检测到新设备的插入,并尝试为其寻找合适的驱动程序。如果 pci_driver 模块已经加载,并且设备的厂商 ID 和设备 ID 与驱动程序支持的列表匹配,内核会调用 probe 函数对设备进行初始化和配置。
开机检测:在系统启动过程中,内核会扫描所有已连接的 PCI 设备,并为它们加载相应的驱动程序。如果 pci_driver 模块已经被添加到内核模块自动加载列表中(可以通过修改 /etc/modules 文件实现),内核会在启动时自动加载该模块,并对匹配的 PCI 设备进行初始化。
2. 设备中断触发
当 PCI 设备产生中断时,会触发驱动程序中的中断服务函数 pci_Mcard_interrupt。设备产生中断的原因可能有很多,例如设备完成了一次数据传输、检测到了某种异常情况等。一旦中断发生,内核会根据之前注册的中断号和中断服务函数,调用 pci_Mcard_interrupt 函数来处理中断。
*/
更多推荐
所有评论(0)