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