嵌入式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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章