LinuxのPCI / PCIeのドライブ

/driver/pci/probe.c

/arch/powerpc/kernel/pci_64.c

機能を求める場合は(読取装置ベンダおよび製品IDによって決定される)が存在する各装置の各機能を走査駆動PCI、PCIコールpci_scan_deviceでは、それは(pci_setup_deviceにより完了)完全pci_devを作成し、デバイス機能、デバイスドライバをロードする場合、デバイスドライバは、バス上に接続されたスキャンバス・デバイスの種類に応じて、デバイス機能のグローバルリストと、リスト内のバスに加え、そして次にかどうかを決定するためにベンダーIDと製品番号pci_devデータ構造を読み出し完成し、独自の製品テーブルIDと比較することにより、デバイスをサポートしています。

 

1. PCIバスドライバとデバイスドライバに駆動します。Linuxカーネルバスドライバは、列挙メインデバイス、従来のアクセスコンフィギュレーション空間の64バイトの完了を完了する。PCIインタフェースのための機能デバイスドライバが実施される特定の機器が必要です。例えば、PCIEカードを駆動する、ネットワークカードは確かにトランシーバを実現します。

 

PCIバスの初期化プロセスは、2つの部分の列挙を含む、登録およびPCI、PCIコントローラ装置、PCIバス及び他のバスは非常に重要な違いは、起動時にPCIバスを横断する、PCIバス列挙される開始2.linux可能なすべてのDEVのFUNCの木、これはプラットフォームに類似する(例えば、デバイスドライバのIDなど)と一致するように登録されたPCI、PCIデバイスドライバ必要、初期化デバイスドライバの背後にある重要な基礎であるように、デバイスのすべての機器のベンダーID名は、それほど存在し、記録ドライブ。

 

3. PCIデバイス名と09名:00.0 0c04:1077:2432 (REV 03)は、 これらの数字は何を表しているかを見てみましょう。
3桁の「09:00.0」の前でそれぞれのどのような意味です。
PCIで3番の識別値を使用して、装置は、1「バス(バス番号)」、2「デバイス(デバイス番号)と3」機能(機能番号)」に個人として使用した。
これだけ09:00.0バス番号= 09であり、デバイス番号= 00関数= 0の
3つの識別子が16ビットに結合され、この数は、
バス(バス番号)が2 ^ 8はFFへバス256(0まで接続することができる8ビット)、  
デバイス(デバイス番号)デバイスの2 ^ 5最大32種類1F(0)にアクセスすることができ、5ビット  
の各デバイスは、8つの機能(0〜7)までを有することができるための機能(機能番号)が2 ^ 3を3ビット。
詳細について情報をhttp://benjr.tw/node/543 #lspci参照
ちょうど0c04として、Linuxデバイスを表現するクラスID +ベンダID +デバイスIDの使用:1077:2432は、代表的なデバイス(クラスID =の名前であります0c04、1077 =ベンダーID、デバイスID = 2432)。
0c04:0c04クラス表現が"ファイバーチャネルコントローラ"であります
1077:ベンダーID 1077メーカー"Qlogicの社"
2432:デバイスID 2432名"ISP2432ベースの4Gbファイバチャネルの PCI ExpressのHBA"

 

前記デバイスは、リンクPCIEの下流に接続することができ、このデバイスの下流には、数0唯一のデバイスであります

 

データパスバス5.は、PCIeの物理リンク(レーン)、差動信号の二組、4本の信号線の合計、レーン構成PCIeリンクの複数から形成することができるされ、現在のPCIeリンクをサポート2 、4-16および32レーン、すなわち、×1、×2、×4、×8、PCIeリンク×12、×16×32、および幅。各バージョンとレーンPCIeバス利用の使用に依存バス周波数。

6. PCIEの研究はほとんどのホットスワップは、追跡カーネルドライバは、今だけでなく、Linuxカーネルのサポートのために、また、必要な支援PCIEデバイス自体(PCIEホットスワップ可能なレジスタ拡張)---、それを発見しました

  6.1 Linuxカーネルのサポート

 

  バスオプション---> PCIホットプラグをサポート 

            ---> PCI Expressのサポート---> PCI Expressのホットプラグドライバ

 

   6.2 PCIeデバイス支持体は、標準的なハンドブックのPCIE 3.0を参照して、レジスタ拡張(スロット機能レジスタ)、及びビット7ホットプラグcapalbeが存在しなければならない1

 

7. RTL8139デバイスドライバの初期化
__init demo_init_module int型の静的(無効)
{
    / *チェックシステムがサポートするPCIバス* /
    IF(pci_present()!)
        戻り-ENODEV;
    / *ハードウェアドライバは、* /登録
    IF(pci_register_driver(&demo_pci_driverを)! ){
        pci_unregister_driver(&demo_pci_driver);
                戻り-ENODEV;
    }
    / * ... * /
   
    リターン0;
}


pci_register_driver 3つのことを行います。


  ①カーネルに登録されていた持ち込まパラメータrtl8139_pci_driverで、カーネルはPCIデバイスの大規模なリストを持って、ここではそこにハングアップするPCIドライバーの責任です。


  :で同一id_table rtl8139_pci_tbl rtl8139_pci_driver識別情報であることが分かっており、次のように定義されている場合②バス構成空間上(PCIカードがデバイス装置属する)全てのPCIデバイスである参照


  静的構造体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、}


  };


  、ドライバを駆動するために使用されていることRealtekの、2製の機器がある場合は機器、ここを見てする必要がありますが、このような、ここで私たちを表す0x10ec Realtekの会社のように、異なるPCIデバイスを、識別するために使用されるカーネルpci_device_id定義された構造であり、我々は、PCIデバイスのコンフィギュレーションスペースをスキャン上へ行った人、それが機能プローブを呼び出すことです。もちろん、我々が持っているすべてのサービスにすることができ、このデバイス用のドライバにこれを説明するために、同社のデバイス番号か何かに他の番号を参照する必要があります。


  ③rtl8139_pci_driverこの構造は、デバイスのデータ構造(pci_dev)に掛けられている、装置を示すと、ドライブからそれ自身を持っています。ドライブはまた、オブジェクトを提供しています見つけます。

 

8. PCIブリッジは一般的に何のプライベートレジスタではありません、オペレーティング・システムは、駆動車軸PCE、ブリッジと呼ばれる透明なブリッジを提供する必要はありません。

 

PCIの研究ノート 

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カーネルのネットワーク管理機器

http://www.linuxidc.com/Linux/2013-08/88472.htm

 

PCIドライバの初期化プロセス - LinuxカーネルのPCI初期化のPOWERPC85xxアーキテクチャをベース

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);

 

公開された407元の記事 ウォンの賞賛150 ビュー380 000 +

おすすめ

転載: blog.csdn.net/ds1130071727/article/details/102625352