1. 程式人生 > >嵌入式Linux裝置驅動開發筆記(一)

嵌入式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);