Linux設備驅動開發流程(轉)


一、Linux設備的分類

 

字符設備、塊設備、網絡設備,三種設備之間的區別是數據的交互模式,分別為:

字節流、數據塊、數據包。

 

二、VFS核心結構體

 

VFS核心結構體定義在"linux/fs.h"頭文件中。

 

1、struct inode結構體

記錄文件的屬主、訪問時間等信息。當第一次打開文件的時候由VFS創建並初始化。當文件的所有引用都退出后,釋放inode; 如果用戶態有多個人同時打開一個文件,則VFS只需要分配一個inode。

 

2、struct file結構體

對應用戶態的open操作。如果多次打開同一個文件,內核會生成多個file。file中記錄文件的打開方式,文件內部指針等。當文件徹底關閉時,釋放file。

 

3、struct file_operations結構體

該結構體包含若干函數指針,這些函數由驅動來實現,並集中到file_operations中,注冊到VFS。

驅動一般要實現的函數有:

open

release

read

write

unlocked_ioctl

驅動可能會實現的有:

poll

mmap

fasync

flush

llseek

 

三、字符設備驅動開發流程

 

(1)確定硬件信息

要確定硬件的數量,物理地址,中斷號等信息;

 

(2)為要支持的設備准備一個私有結構體

內核並不要求必須有私有結構體,但如果驅動支持多個設備,最好設計一個。私有結構體完全由驅動人員自行設計,一般來說,會把和設備相關的信息寫入該結構體,比如設備的地址等。

 

(3)為要支持的每個設備分配對應的設備號

設備號由char驅動分配,要求唯一。一般來說,如果char驅動可支持多個類似的設備,則應該為這些設備選擇一個主設備號,然后為每個設備選擇一個特定的次設備號。盡量挑選和其他驅動不一樣的主設備號。可以看/proc/devices,文件中記錄了其他驅動選擇的主設備號;也可以向內核申請,由內核分配一個主設備號。

 

#define DEV_MAJOR 50

...

dev_id = MKDEV(DEV_MAJOR, 0);

(4)准備file_operations結構體

函數集中包括open/release/read/write/unlocked_ioctl等函數,如果驅動支持多個設備,在函數中必須能區分自己訪問的是哪個設備。

 

static struct file_operations mem_fops = {

.owner = THIS_MODULE,

.open = mem_open,

.release = mem_release,

.read = mem_read,

.write = mem_write,

.unlocked_ioctl = mem_ioctl,

};

(5)注冊設備

 

方法一:

 

利用cdev結構體,將設備號和file_operations注冊到VFS。一般來說,將cdev結構體包含到私有結構體中。采用cdev注冊的設備,不會自動創建設備文件。

 

cdev_init(&mem_cdev, &mem_fops);

cdev_add(&mem_cdev, dev_id, 1);

cdev_del(&mem_cdev); //注銷cdev

方法二:

 

注冊miscdevice(用misc代替cdev注冊,可以自動創建設備文件)

這種情況下不需要定義主設備號,即省去#define DEV_MAJOR 50,同時需要用頭文件"linux/miscdevice.h"代替"linux/cdev.h"頭文件。

 

static struct miscdevice mem_miscdev = {

.minor = MISC_DYNAMIC_MINOR,

.name = "mem", //對應/dev/mem設備文件

.fops = &mem_fops,

};

 

ret = misc_register(&mem_miscdev); //注冊miscdevice

misc_deregister(&mem_miscdev); //注銷miscdevice

cdev和miscdevice比較:

(1)cdev可以實現同一個驅動對應多個設備,而miscdevice只能實現一個驅動對應一個設備;

(2)cdev不能實現設備文件的自動創建,而miscdevice可以實現設備文件的自動創建。

 

上述的過程比較適合較簡單的設備,比如看門狗,led燈,各種傳感器等。較復雜設備的char驅動,常常要利用內核提供的驅動子系統代碼進行設計。

 

四、如何在linux驅動中訪問寄存器(SFR)

 

1、片內外設(pripheral)

(1)基於三總線訪問

(2)用寄存器控制

(3)寄存器有物理地址,可通過手冊查到,不可更改

 

2、片外外設

(1)很少通過三總線相連,一般是通過UART,CAN,I2C,USB,SPI,MIPI,I2S,AC97等總線相連。主芯片內部必須提供對應的控制器:內部的控制器 <–> 外部的外設

(2)片外外設基本都是智能設備(不但有硬件,還有軟件)

(3)有些片外外設通過寄存器控制,有些則通過命令控制

(4)如果用寄存器控制,寄存器沒有物理地址,只有偏移。

(5)要控制片外外設,需要首先了解對應的總線

 

3、訪問寄存器的流程

由於linux使能了MMU,因此對於驅動來說,不能直接使用寄存器的物理地址,必須將其映射為虛擬地址才可以使用。

 

(1)定義寄存器物理基地址以及寄存器的偏移

 

#define GPIO_BASE 0x11000000

#define GPIO_SIZE 0x1000 //0x8

#define GPM4CON 0x2E0 //偏移地址

#define GPM4DAT 0x2E4 //偏移地址

GPIO_SIZE為寄存器的范圍,可以按照使用的寄存器的總大小進行計算,比如用了兩個寄存器,范圍是0x8;但由於地址映射的最小單位是4K,因此小於4K的值都是可以的。

 

(2)將寄存器物理地址映射到虛擬地址,如果映射不成功,則無法訪問寄存器

 

static void __iomem *vir_base;

vir_base = ioremap(GPIO_BASE, GPIO_SIZE);

if (!vir_base) {

printk("Cannot ioremap\n");

return -EIO;

}

(3)訪問寄存器,一般采用基地址加偏移的模式,內核根據寄存器的大小,提供了一系列函數

 

8位寄存器的訪問

 

char value;

value = readb(vir_base + offset);

writeb(value, (vir_base + offset));

16位寄存器的訪問

 

short value;

value = readw(vir_base + offset);

writew(value, (vir_base + offset));

32位寄存器的訪問

 

int value;

value = readl(vir_base + offset);

writel(value, (vir_base + offset));

64位寄存器的訪問

 

u64 value;

value = readq(vir_base + offset);

writeq(value, (vir_base + offset));

(4)取消寄存器的映射

 

iounmap(vir_base);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM