Linux pci/pcie驱动
/driver/pci/probe.c/arch/powerpc/kernel/pci_64.c在pci驱动中pci调用pci_scan_device扫描每个设备的每个功能,当发现该功能存在时(通过读设备的vendor及product ID确定),就为该设备功能建立一个完整的pci_dev(通过pci_setup_device 完成),并将该设备功能加入到全局链表及总线链表中,当加载设备驱...
/driver/pci/probe.c
/arch/powerpc/kernel/pci_64.c
在pci驱动中pci调用pci_scan_device扫描每个设备的每个功能,当发现该功能存在时(通过读设备的vendor及product ID确定),就为该设备功能建立一个完整的pci_dev(通过pci_setup_device 完成),并将该设备功能加入到全局链表及总线链表中,当加载设备驱动程序时,设备驱动根据总线类型扫描总线上连接的设备,然后读取pci_dev数据结构中的vendor ID及product Id号来确定是否支持该设备通过与自己的 product table id 比较完成。
1. pci驱动分为总线驱动和设备驱动。总线驱动是linux内核完成,主要完成设备的枚举,常规64个字节配置空间的访问。设备驱动是针对PCI接口具体设备需要实现的功能。例如PCIE网卡的驱动,肯定是实现一个网卡的收发。
2.linux启动过程中pci总线初始化主要包括2部分,pci控制器的注册和pci设备的枚举,pci总线和其他总线一个很重要的区别就是pci总线的枚举,在启动过程中遍历pci总线树上所有可能的dev func,记录下所有存在的设备的vendor id 设备名等,这个是做为后面pci设备驱动初始化中注册pci设备驱动需要匹配(例如设备驱动id)的重要依据,类似于platform驱动。
3. PCI 名稱以及裝置名稱為 09:00.0 0c04: 1077:2432 (rev 03) 先來看看這些數字所代表的意義.
前面的 3 個數字 "09:00.0" 是各代表什麼意思.
在 PCI 的裝置使用三個編號用來當作識別值,個別為 1. "匯流排(bus number)", 2. "裝置(device number) 以及 3. "功能(function number)".
所以剛剛的 09:00.0 就是 bus number = 09 ,device number = 00 function = 0 .
這3個編號會組合成一個 16-bits 的識別碼,
匯流排(bus number) 8bits 2^8 至多可連接 256 個匯流排(0 to ff),
裝置(device number) 5bits 2^5 至多可接 32 種裝置(0 to 1f) 以及
功能(function number) 3bits 2^3 至多每種裝置可有 8 項功能(0 to 7).
關於更多 #lspci 的資訊請參考 http://benjr.tw/node/543
不過在 Linux 使用 Class ID + Vendor ID + Device ID 來代表裝置,如剛剛的 0c04: 1077:2432 所代表裝置名稱為 (Class ID = 0c04 ,Vendor ID = 1077,Device ID =2432) .
0c04 : class 0c04 表示是 "Fiber Channel controller"
1077 : vendor ID 1077 製造廠商 "Qlogic Corp"
2432 : device ID 2432 產品名稱 "ISP2432-based 4Gb Fiber Channel to PCI Express HBA"
4.在PCIE链路只能连接一个下游设备,而这个下游设备的Device Number只能为0
5.在PCIe总线的物理链路的一个数据通路(Lane)中,由两组差分信号,共4根信号线组成, PCIe链路可以由多条Lane组成,目前PCIe链路可以支持1、2、4、8、12、16和32个Lane,即×1、×2、×4、×8、×12、×16和×32宽度的PCIe链路。每一个Lane上使用的总线频率与PCIe总线使用的版本相关。
6.研究了一下PCIE热插拔,跟踪内核驱动发现,目前来看,不仅需要linux内核支持,还需要PCIE设备本身支持(PCIE支持热插拔寄存器扩展)---
6.1 linux内核支持
Bus options ---> support for PCI Hotplug
---> PCI Express support ---> PCI EXpress hotplug driver
6.2 PCIE设备支持,参考PCIE 3.0标准手册,必须有该寄存器扩展(slot capabilities Register),且第7位hot- plug capalbe为1
7. rtl8139 设备驱动初始化
static int __init demo_init_module (void)
{
/* 检查系统是否支持PCI总线 */
if (!pci_present())
return -ENODEV;
/* 注册硬件驱动程序 */
if (!pci_register_driver(&demo_pci_driver)) {
pci_unregister_driver(&demo_pci_driver);
return -ENODEV;
}
/* ... */
return 0;
}
pci_register_driver做了三件事情。
①是把带过来的参数rtl8139_pci_driver在内核中进行了注册,内核中有一个PCI设备的大的链表,这里负责把这个PCI驱动挂到里面去。
②是查看总线上所有PCI设备(网卡设备属于PCI设备的一种)的配置空间如果发现标识信息与rtl8139_pci_driver中的id_table相同即rtl8139_pci_tbl,而它的定义如下:
static struct pci_device_id rtl8139_pci_tbl[] __devinitdata = {
{0x10ec, 0x8129, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1},
{PCI_ANY_ID, 0x8139, 0x10ec, 0x8139, 0, 0,0 },
{0,}
};
,那么就说明这个驱动程序就是用来驱动这个设备的,这里需要注意一下pci_device_id是内核定义的用来辨别不同PCI设备的一个结构,例如在我们这里0x10ec代表的是Realtek公司,我们扫描PCI设备配置空间如果发现有Realtek公司制造的设备时,两者就对上了,就是调用probe函数了。当然对上了公司号后还得看其他的设备号什么的,都对上了才说明这个驱动是可以为这个设备服务的。
③是把这个rtl8139_pci_driver结构挂在这个设备的数据结构(pci_dev)上,表示这个设备从此就有了自己的驱动了。而驱动也找到了它服务的对象了。
8. pci桥一般不存在私有寄存器,操作系统也不需要为PCE桥提供驱动,这类桥称为透明桥
http://blog.chinaunix.net/uid-24148050-id-101021.html
Linux下的PCI总线驱动
http://blog.csdn.net/weiqing1981127/article/details/8031541
Linux PCI网卡驱动的详细分析
http://soft.chinabyte.com/os/13/12304513.shtml
Linux kernel中网络设备的管理
http://www.linuxidc.com/Linux/2013-08/88472.htm
PCI驱动初始化流程--基于POWERPC85xx架构的Linux内核PCI初始化
http://blog.csdn.net/luwei860123/article/details/38816473
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
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\n");
result = -EIO;
goto enable_device_err;
}
//获得bar0的物理地址和虚拟地址
bar0_phy = pci_resource_start(pdev,0);
if(bar0_phy<0){
DEBUG_ERR("failed:pci_resource_start\n");
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;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
SetPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
}
} else {
goto free_bar0;
}
//申请一块DMA内存,作为目的地址,在进行DMA读写的时候会用到。
dma_dst_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_dst_phy);
if(dma_dst_vir!=0){
for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
SetPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
}
} else {
goto alloc_dma_src_err;
}
//使能msi,然后才能得到pdev->irq
result = pci_enable_msi(pdev);
if (unlikely(result)){
DEBUG_ERR("failed:pci_enable_msi\n");
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\n");
goto enable_msi_error;
}
//DMA 的读写初始化
dma_init();
enable_msi_error:
pci_disable_msi(pdev);
alloc_dma_dst_err:
for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
}
pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);
alloc_dma_src_err:
for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
}
pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);
free_bar0:
iounmap((void *)bar0_vir);
request_regions_err:
pci_release_regions(pdev);
enable_device_err:
pci_disable_device(pdev);
end:
return result;
}
static void hello_remove(struct pci_dev *pdev)
{
int i;
free_irq(pdev->irq,my_device.pci_dev);
pci_disable_msi(pdev);
for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
}
pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);
for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
}
pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);
iounmap((void *)bar0_vir);
pci_release_regions(pdev);
pci_disable_device(pdev);
}
//难点三:中断响应设置
static irqreturn_t hello_interrupt(int irq, void * dev)
{
//1.该中断调用时机:当DMA完成的时候,会往msi_addr中写入msi_data,从而产生中断调用这个函数
//2.根据DMA Channel control 1 register寄存器的状态,判断读写状态,读失败,写失败,读成功,写成功,做出不同的处理。
return 0;
}
static struct pci_driver hello_driver = {
.name = DEV_NAME,
.id_table = hello_ids,
.probe = hello_probe,
.remove = hello_remove,
};
static int hello_open(struct inode *inode, struct file *file)
{
printk("driver: hello_open\n");
//填写产品的逻辑
return 0;
}
int hello_close(struct inode *inode, struct file *file)
{
printk("driver: hello_close\n");
//填写产品的逻辑
return 0;
}
long hello_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
//填写产品的逻辑
//为应用层提供的函数接口,通过解析cmd,在switch中做出不同的处理。
iATU_bar0();//某个合适的地方调用
return 0;
}
//难点二:启动dma的读写(read和write函数).
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_close,
.unlocked_ioctl = hello_unlocked_ioctl,
};
static int hello_drv_init(void)
{
int ret;
ret = pci_register_driver(&hello_driver);
if (ret < 0) {
printk("failed: pci_register_driver\n");
return ret;
}
ret=alloc_chrdev_region(&my_device.devno,0,DEVICE_NUMBER,"hello");
if (ret < 0) {
printk("failed: register_chrdev_region\n");
return ret;
}
cdev_init(&my_device.cdev, &hello_fops);
ret = cdev_add(&my_device.cdev, my_device.devno, DEVICE_NUMBER);
if (ret < 0) {
printk("faield: cdev_add\n");
return ret;
}
hello_class = class_create(THIS_MODULE, "hello_class");
hello_class_dev = device_create(hello_class, NULL, my_device.devno, NULL, "hello_device");
return 0;
}
static void hello_drv_exit(void)
{
device_destroy(hello_class,my_device.devno);
class_destroy(hello_class);
cdev_del(&(my_device.cdev));
unregister_chrdev_region(my_device.devno,DEVICE_NUMBER);
pci_unregister_driver(&hello_driver);
}
module_init(hello_drv_init);
module_exit(hello_drv_exit);
更多推荐
所有评论(0)