姓名:李萌怡? 學(xué)號:19020100103? 學(xué)院:電子工程學(xué)院
轉(zhuǎn)自:https://blog.csdn.net/light_in_dark/article/details/73321105
【嵌牛導(dǎo)讀】:對于嵌入式系統(tǒng)的學(xué)習(xí)來說,學(xué)習(xí)嵌入式Linux驅(qū)動開發(fā)是十分重要的環(huán)節(jié)。本文對嵌入式linux設(shè)備系統(tǒng)開發(fā)作以簡要總結(jié)。
【嵌牛鼻子】:寄存器? 結(jié)構(gòu)體
【嵌牛提問】:字符設(shè)備驅(qū)動開發(fā)流程有哪些?
【嵌牛正文】
一、Linux設(shè)備的分類
字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備,三種設(shè)備之間的區(qū)別是數(shù)據(jù)的交互模式,分別為:
字節(jié)流、數(shù)據(jù)塊、數(shù)據(jù)包。
二、VFS核心結(jié)構(gòu)體
VFS核心結(jié)構(gòu)體定義在”linux/fs.h”頭文件中。
1、struct inode結(jié)構(gòu)體
記錄文件的屬主、訪問時間等信息。當(dāng)?shù)谝淮未蜷_文件的時候由VFS創(chuàng)建并初始化。當(dāng)文件的所有引用都退出后,釋放inode; 如果用戶態(tài)有多個人同時打開一個文件,則VFS只需要分配一個inode。
2、struct file結(jié)構(gòu)體
對應(yīng)用戶態(tài)的open操作。如果多次打開同一個文件,內(nèi)核會生成多個file。file中記錄文件的打開方式,文件內(nèi)部指針等。當(dāng)文件徹底關(guān)閉時,釋放file。
3、struct file_operations結(jié)構(gòu)體
該結(jié)構(gòu)體包含若干函數(shù)指針,這些函數(shù)由驅(qū)動來實(shí)現(xiàn),并集中到file_operations中,注冊到VFS。
驅(qū)動一般要實(shí)現(xiàn)的函數(shù)有:
open
release
read
write
unlocked_ioctl
驅(qū)動可能會實(shí)現(xiàn)的有:
poll
mmap
fasync
flush
llseek
三、字符設(shè)備驅(qū)動開發(fā)流程
(1)確定硬件信息
要確定硬件的數(shù)量,物理地址,中斷號等信息;
(2)為要支持的設(shè)備準(zhǔn)備一個私有結(jié)構(gòu)體
內(nèi)核并不要求必須有私有結(jié)構(gòu)體,但如果驅(qū)動支持多個設(shè)備,最好設(shè)計(jì)一個。私有結(jié)構(gòu)體完全由驅(qū)動人員自行設(shè)計(jì),一般來說,會把和設(shè)備相關(guān)的信息寫入該結(jié)構(gòu)體,比如設(shè)備的地址等。
(3)為要支持的每個設(shè)備分配對應(yīng)的設(shè)備號
設(shè)備號由char驅(qū)動分配,要求唯一。一般來說,如果char驅(qū)動可支持多個類似的設(shè)備,則應(yīng)該為這些設(shè)備選擇一個主設(shè)備號,然后為每個設(shè)備選擇一個特定的次設(shè)備號。盡量挑選和其他驅(qū)動不一樣的主設(shè)備號??梢钥?proc/devices,文件中記錄了其他驅(qū)動選擇的主設(shè)備號;也可以向內(nèi)核申請,由內(nèi)核分配一個主設(shè)備號。
#define DEV_MAJOR? 50
...
dev_id = MKDEV(DEV_MAJOR, 0);
(4)準(zhǔn)備file_operations結(jié)構(gòu)體
函數(shù)集中包括open/release/read/write/unlocked_ioctl等函數(shù),如果驅(qū)動支持多個設(shè)備,在函數(shù)中必須能區(qū)分自己訪問的是哪個設(shè)備。
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)注冊設(shè)備
方法一:
利用cdev結(jié)構(gòu)體,將設(shè)備號和file_operations注冊到VFS。一般來說,將cdev結(jié)構(gòu)體包含到私有結(jié)構(gòu)體中。采用cdev注冊的設(shè)備,不會自動創(chuàng)建設(shè)備文件。
cdev_init(&mem_cdev, &mem_fops);
cdev_add(&mem_cdev, dev_id, 1);
cdev_del(&mem_cdev);? //注銷cdev?
方法二:
注冊miscdevice(用misc代替cdev注冊,可以自動創(chuàng)建設(shè)備文件)
這種情況下不需要定義主設(shè)備號,即省去#define DEV_MAJOR 50,同時需要用頭文件”linux/miscdevice.h”代替”linux/cdev.h”頭文件。
static struct miscdevice mem_miscdev = {
? ? .minor? = MISC_DYNAMIC_MINOR,
? ? .name? = "mem",? ? //對應(yīng)/dev/mem設(shè)備文件
? ? .fops? = &mem_fops,
};
ret = misc_register(&mem_miscdev); //注冊miscdevice
misc_deregister(&mem_miscdev);? ? //注銷miscdevice
cdev和miscdevice比較:
(1)cdev可以實(shí)現(xiàn)同一個驅(qū)動對應(yīng)多個設(shè)備,而miscdevice只能實(shí)現(xiàn)一個驅(qū)動對應(yīng)一個設(shè)備;
(2)cdev不能實(shí)現(xiàn)設(shè)備文件的自動創(chuàng)建,而miscdevice可以實(shí)現(xiàn)設(shè)備文件的自動創(chuàng)建。
上述的過程比較適合較簡單的設(shè)備,比如看門狗,led燈,各種傳感器等。較復(fù)雜設(shè)備的char驅(qū)動,常常要利用內(nèi)核提供的驅(qū)動子系統(tǒng)代碼進(jìn)行設(shè)計(jì)。
四、如何在linux驅(qū)動中訪問寄存器(SFR)
1、片內(nèi)外設(shè)(pripheral)
(1)基于三總線訪問
(2)用寄存器控制
(3)寄存器有物理地址,可通過手冊查到,不可更改
2、片外外設(shè)
(1)很少通過三總線相連,一般是通過UART,CAN,I2C,USB,SPI,MIPI,I2S,AC97等總線相連。主芯片內(nèi)部必須提供對應(yīng)的控制器:內(nèi)部的控制器 <–> 外部的外設(shè)
(2)片外外設(shè)基本都是智能設(shè)備(不但有硬件,還有軟件)
(3)有些片外外設(shè)通過寄存器控制,有些則通過命令控制
(4)如果用寄存器控制,寄存器沒有物理地址,只有偏移。
(5)要控制片外外設(shè),需要首先了解對應(yīng)的總線
3、訪問寄存器的流程
由于linux使能了MMU,因此對于驅(qū)動來說,不能直接使用寄存器的物理地址,必須將其映射為虛擬地址才可以使用。
(1)定義寄存器物理基地址以及寄存器的偏移
#define GPIO_BASE? 0x11000000
#define GPIO_SIZE? 0x1000? //0x8
#define GPM4CON? ? 0x2E0? //偏移地址
#define GPM4DAT? ? 0x2E4? //偏移地址
GPIO_SIZE為寄存器的范圍,可以按照使用的寄存器的總大小進(jìn)行計(jì)算,比如用了兩個寄存器,范圍是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)訪問寄存器,一般采用基地址加偏移的模式,內(nèi)核根據(jù)寄存器的大小,提供了一系列函數(shù)
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);
————————————————
版權(quán)聲明:本文為CSDN博主「light_in_dark」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/light_in_dark/article/details/73321105