1. 程式人生 > >linux裝置驅動之PCIE驅動開發

linux裝置驅動之PCIE驅動開發

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

相關推薦

linux裝置驅動PCIE驅動開發

#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/signal.h> #include <linux/init.h>

Linux裝置模型tty驅動架構分析

------------------------------------------ 本文系本站原創,歡迎轉載!轉載請註明出處:http://ericxiao.cublog.cn/------------------------------------------一:前言Tty這個名稱源於電傳打位元組的簡稱。

linux裝置模型uart驅動架構分析

一:前言 接著前面的終端控制檯分析,接下來分析serial的驅動.在linux中,serial也對應著終端,通常被稱為串列埠終端.在shell上,我們看到的/dev/ttyS*就是串列埠終端所對應的裝置節點. 在分析具體的serial驅動之前.有必要先分析uart

Linux裝置模型tty&&uart驅動架構分析

五: uart_add_one_port()操作 在前面提到.在對uart裝置檔案過程中.會將操作轉換到對應的port上,這個port跟uart_driver是怎麼關聯起來的呢?這就是uart_add_ont_port()的主要工作了. 顧名思義,這個函式是在uart_driver增加一個port.程式碼如

Linux 核心裝置驅動GPIO驅動GPIO sysfs支援

需要核心配置CONFIG_GPIO_SYSFS int gpiochip_sysfs_register(struct gpio_device *gdev) {  struct device *dev;  struct device *parent;  struct gpi

Linux 核心裝置驅動GPIO驅動GPIO 管腳新增

在配置CONFIG_OF_GPIO下作用: int of_gpiochip_add(struct gpio_chip *chip) {  int status; if ((!chip->of_node) && (chip->parent))

linux裝置模型匯流排 裝置驅動

《Linux核心修煉之道》讀書筆記 1、 裝置模型的上層建築由匯流排(bus) 、裝置(device)、 驅動(device_driver)這3個數據結構構成,裝置模型表示了它們之間的連線關係。

linux設備驅動misc驅動框架源碼分析(一)

linux驅動開發misc設備驅動 1、misc設備驅動框架源碼部分是由內核開發者實現提供的,主要是創建misc類和為驅動開發者提供misc_register函數,來進行創建misc設備。 這部分的源碼在/drvier/char/misc.c裏,代碼如下:/* * linux/drivers/c

linux設備驅動misc驅動框架源碼分析(二)

linux驅動開發misc設備驅動1、misc_open函數分析 該函數在driver/char/misc.c中,misc.c是驅動框架實現的,這裏面的misc_Open函數是misc驅動框架為應用層提供的一個打開misc設備的一個接口。 1、首先我們要知道在misc.c中的misc_init函數

十三、Linux驅動觸控式螢幕驅動

1. 基本概念     常用的觸控式螢幕型別有兩種:阻性觸控式螢幕和容性觸控式螢幕。阻性觸控式螢幕是一種感測器,它將矩形區域中觸控點(X, Y)的物理位置轉換為代表X座標和Y座標的電壓。觸控式螢幕包含上下疊合的兩個透明層阻性材料,中間由一種彈性材料隔開。當觸控式螢幕

十二、Linux驅動LCD驅動

1. 基本概念     LCD是Liquid Crystal Display的簡稱,也就是經常所說的液晶顯示器。LCD能夠支援彩色影象的顯示和視訊的播放,是一種非常重要的輸出裝置。如果我們的系統要用GUI(圖形介面介面),比如minigui,MicroWindows

04-Linux裝置樹系列-GPIO驅動實踐

1. 前言 GPIO驅動開發可能算是Linux核心裝置驅動開發中最為簡單、最常見的一個方向,對於開發板的按鍵、LED、蜂鳴器、電源控制等模組,可能都是使用GPIO實現的。Linux核心的GPIO子系統在核心不斷的演進過程中進行了多次的重構,本文的第二

linux I2C 驅動----i2c驅動的註冊過程(i2c_register_driver->driver_register(&driver->driver)->driver_find)

Linux下i2c驅動的載入過程,分為i2c裝置層、i2c adapter層與i2c核心層 i2c裝置驅動層也就是我們為特定i2c裝置編寫的驅動,下面是我自己理解的i2c驅動的註冊過程 在我們寫的i2c裝置驅動中,我們會呼叫i2c_add_driver()開始i2c裝

Linux音訊驅動ASoC驅動架構

1.  ASoC的由來 ASoC--ALSA System on Chip ,是建立在標準ALSA驅動層上,為了更好地支援嵌入式處理器和移動裝置中的音訊Codec的一套軟體體系。在ASoc出現之前,核心對於SoC中的音訊已經有部分的支援,不過會有一些侷限性:    Codec驅動與SoC CPU的底層

Linux核心模組驅動---led驅動

/**************************************************************************/ /***************************led.c********************************************

Linux裝置、匯流排和驅動之間的關係

(一)、驅動、匯流排和裝置的主要資料結構 (include/linux/device.h) (/driver/base/base.h)   (include/device.h) 匯流排中的那兩條連結串列是怎麼形成的。核心要求每次出現一個裝置就要向匯流排彙報,或者

linux驅動USB驅動程式框架

USB驅動程式框架: app: -----------------------------------------------------------------------                              USB裝置驅動程式         

第18章 ARM Linux裝置四(常用的OF API)

18.4 常用的OF API除了前文介紹的of_machine_is_compatible()、of_device_is_compatible()等常用函式以外,在Linux的BSP和驅動程式碼中,經常會使用到一些Linux中其他裝置樹的API,這些API通常被冠以of_字首

觸控式螢幕驅動編寫驅動程式

我們開啟我們的核心板原理圖可以看到這四根引腳分別接在xadcAIN2,3,4,5上面 搜尋發現不需要配置什麼暫存器 我們看一看晶片手冊上的觸控式螢幕那一章,我們的工作就是閱讀那一章然後弄清楚那裡面的東西就可以了 先來看一看核心自帶的觸控式螢幕驅動做了什麼事情 ts.clo

網絡卡驅動02驅動原始碼分析

0 環境 核心:經過xilinx基於zynq平臺定製的4.4.0系核心; 硬體:zynq晶片,其中mac contorller是使用Cadence的IP核,phy晶片使用提marvell的1116R晶