PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S,因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和网络等领域。PCIE插槽是可以向下兼容的,比如PCIE 1X接口可以插4X、8X、16X的插槽上。
实现基本的PCIE驱动程序,实现以下模块:初始化设备、设备打开、数据读写和控制、中断处理、设备释放、设备卸载。本程序适合PCIE驱动开发通用调试的基本框架,对于具体PCIE设备,需要配置相关寄存器才可以使用!
源代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("pcie device driver");
#define DEV_NAME "hello_pcie"
#define DEBUG
#ifdef DEBUG
#define DEBUG_ERR(format,args...)
do{
printk("[%s:%d] ",__FUNCTION__,__LINE__);
printk(format,##args);
}while(0)
#else
#define DEBUG_PRINT(format,args...)
#endif
//1M
#define DMA_BUFFER_SIZE 1*1024*1024
#define FASYNC_MINOR 1
#define FASYNC_MAJOR 244
#define DEVICE_NUMBER 1
static struct class * hello_class;
static struct device * hello_class_dev;
struct hello_device
{
struct pci_dev* pci_dev;
struct cdev cdev;
dev_t devno;
}my_device;
//barn(n=0,1,2或者0,1,2,3,4,5) 空间的物理地址,长度,虚拟地址
unsigned long bar0_phy;
unsigned long bar0_vir;
unsigned long bar0_length;
unsigned long bar1_phy;
unsigned long bar1_vir;
unsigned long bar1_length;
//进行DMA转换时,dma的源地址和目的地址
dma_addr_t dma_src_phy;
dma_addr_t dma_src_vir;
dma_addr_t dma_dst_phy;
dma_addr_t dma_dst_vir;
//根据设备的id填写,这里假设厂商id和设备id
#define HELLO_VENDOR_ID 0x666
#define HELLO_DEVICE_ID 0x999
static struct pci_device_id hello_ids[] = {
{HELLO_VENDOR_ID,HELLO_DEVICE_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},
{0,}
};
MODULE_DEVICE_TABLE(pci,hello_ids);
static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id);
static void hello_remove(struct pci_dev *pdev);
static irqreturn_t hello_interrupt(int irq, void * dev);
//往iATU写数据的函数
void iATU_write_config_dword(struct pci_dev *pdev,int offset,int value)
{
}
//假设需要将bar0映射到内存
static void iATU_bar0(void)
{
//下面几步,在手册中有example
//iATU_write_config_dword(my_device.pci_dev,iATU Lower Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存
//iATU_write_config_dword(my_device.pci_dev,iATU Upper Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存
//iATU_write_config_dword(my_device.pci_dev,iATU Control 1,0x0);//映射的时内存,所以写0x0
//iATU_write_config_dword(my_device.pci_dev,iATU Control 2,xxx);//使能某个region,开始地址转换
}
//往dma配置寄存器中读写数据的函数,这是难点一:dma寄存器的寻址。
int dma_read_config_dword(struct pci_dev *pdev,int offset)
{
int value =0;
return value;
}
void dma_write_config_dword(struct pci_dev *pdev,int offset,int value)
{
}
void dma_init(void)
{
int pos;
u16 msi_control;
u32 msi_addr_l;
u32 msi_addr_h;
u32 msi_data;
//1.dma 通道0 写初始化 。如何访问DMA global register 寄存器组需要根据具体的硬件,可以通过pci_write/read_config_word/dword,
//也可以通过某个bar,比如通过bar0+偏移量访问。
//1.1 DMA write engine enable =0x1,这里请根据自己的芯片填写
//dma_write_config_dword(->pci_dev,DMA write engine enable,0x1);
//1.2 获取msi能力寄存器的地址
pos =pci_find_capability(my_device.pci_dev,PCI_CAP_ID_MSI);
//1.3 读取msi的协议部分,得到pci设备是32位还是64位,不同的架构msi data寄存器地址同
pci_read_config_word(my_device.pci_dev,pos+2,&msi_control);
//1.4 读取msi能力寄存器组中的地址寄存器的值
pci_read_config_dword(my_device.pci_dev,pos+4,&msi_addr_l);
//1.5 设置 DMA write done IMWr Address Low.这里请根据自己的芯片填写
//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address Low,msi_addr_l);
//1.6 设置 DMA write abort IMWr Address Low.这里请根据自己的芯片填写
//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address Low,msi_addr_l);
if(msi_control&0x80){
//64位的
//1.7 读取msi能力寄存器组中的高32位地址寄存器的值
pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_addr_h);
//1.8 读取msi能力寄存器组中的数据寄存器的值
pci_read_config_dword(my_device.pci_dev,pos+0xc,&msi_data);
//1.9 设置 DMA write done IMWr Address High.这里请根据自己的芯片填写
//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address High,msi_addr_h);
//1.10 设置 DMA write abort IMWr Address High.这里请根据自己的芯片填写
//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address High,msi_addr_h);
} else {
//1.11 读取msi能力寄存器组中的数据寄存器的值
pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_data);
}
//1.12 把数据寄存器的值写入到dma的控制寄存器组中的 DMA write channel 0 IMWr data中
//dma_write_config_dword(my_device.pci_dev,DMA write channel 0 IMWr data,msi_data);
//1.13 DMA channel 0 control register 1 = 0x4000010
//dma_write_config_dword(my_device.pci_dev,DMA channel 0 control register 1,0x4000010);
//2.dma 通道0 读初始化 和上述操作类似,不再叙述。
}
static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int i;
int result;
//使能pci设备
if (pci_enable_device(pdev)){
result = -EIO;
goto end;
}
pci_set_master(pdev);
my_device.pci_dev=pdev;
if(unlikely(pci_request_regions(pdev,DEV_NAME))){
DEBUG_ERR("failed:pci_request_regions
");
result = -EIO;
goto enable_device_err;
}
//获得bar0的物理地址和虚拟地址
bar0_phy = pci_resource_start(pdev,0);
if(bar0_phy<0){
DEBUG_ERR("failed:pci_resource_start
");
result =-EIO;
goto request_regions_err;
}
//假设bar0是作为内存,流程是这样的,但是在本程序中不对bar0进行任何操作。
bar0_length = pci_resource_len(pdev,0);
if(bar0_length!=0){
bar0_vir = (unsigned long)ioremap(bar0_phy,bar0_length);
}
//申请一块DMA内存,作为源地址,在进行DMA读写的时候会用到。
dma_src_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_src_phy);
if(dma_src_vir != 0){
for(i=0;iirq
result = pci_enable_msi(pdev);
if (unlikely(result)){
DEBUG_ERR("failed:pci_enable_msi
");
goto alloc_dma_dst_err;
}
result = request_irq(pdev->irq, hello_interrupt, 0, DEV_NAME, my_device.pci_dev);
if (unlikely(result)){
DEBUG_ERR("failed:request_irq
");
goto enable_msi_error;
}
//DMA 的读写初始化
dma_init();
enable_msi_error:
pci_disable_msi(pdev);
alloc_dma_dst_err:
for(i=0;iirq,my_device.pci_dev);
pci_disable_msi(pdev);
for(i=0;i
驱动示例:https://blog.csdn.net/u010872301/article/details/78519371?locationNum=8&fps=1
驱动详解:https://blog.csdn.net/fengyv/article/details/813775