日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux设备驱动的实现与理解

發布時間:2023/12/31 linux 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux设备驱动的实现与理解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

linux設備驅動的實現與理解

????在linux中對字符設備的驅動編寫,驅動插入以及使用驅動文件進行邏輯控制,其中這份代碼寫在嵌入式板中,通過控制io來實現燈的亮滅,但是設備驅動的實現流程與燈無關,大致的流程都體現在代碼中。我感覺這份博客我自己不會看,太難看了,算是自己對設備驅動理解的記錄吧。

一、???程序解讀

1、????? 系統調用

open() 打開文件

ioctl()設備驅動程序中對設備的I/O通道進行管理的函數

exit() 使進程停止運行,exit()函數在調用exit系統調用之前要檢查文件的打開情況,把文件緩沖區中的內容寫回文件,就是”清理I/O緩沖”。

2、????? 寄存器地址

#define FS4412_GPF3CON? 0x114001E0

通過數據手冊可以找到每個端口的物理地址。

gpf3con = ioremap(FS4412_GPF3CON, 4);

在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常并沒有為這些已知的外設I/O內存資源的物理地址預定義虛擬地址范圍,驅動程序并不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到虛地址空間內,然后才能根據映射所得到的虛地址范圍,通過訪問內存指令訪問這些I/O內存資源。通過ioremap來進行靜態映射,4表示映射一個寄存器的地址,也就是4個字節,返回虛地址保存在本地全局的指針變量中。

3、????? 寄存器讀寫代碼

writel((readl(gpf3con) & ~(0xff << 16)) | (0x11 << 16),gpf3con);

?? writel(readl(gpx2dat) &~(0x3<<4), gpf3dat);

這是初始化端口時的f端口控制寄存器的初始化以及數據寄存器的初試化,其他的端口初始化同樣的方法:

(1)首先根據數據手冊初始化控制寄存器,配置固定的位,將需要的引腳的狀態改為輸出狀態。

(2)通過寫數據寄存器初始化引腳為低電平,即使led燈初始狀態為熄滅。

(3)在收到測試文件的命令后,根據命令相應輸出引腳為低電平或高電平

(4)先將端口數據寄存器32個引腳數據全部讀出,然后只將需要的位置1或清零,然后再寫回端口數據寄存器,可以防止修改沒有用到的引腳。

二、? ?原理

在這里將實驗的原理描述為兩個部分,第一部分為驅動的加載部分,第二部分為驅動加載完成后,驅動的使用時的調用過程。

驅動加載:

1、將驅動源代碼編譯后,生成ko文件,這是將要加載的驅動模塊。2、調用命令insmod加載模塊,首先會找代碼里邊固定的宏moudle_init()來找到驅動中的初始化函數這里是s5pv210_led_init()和退出函數s5pv210_led_exit()。

3、調用s5pv210_led_init()來進行設備號注冊,和設備添加。MKDEV是一個宏,可以通過移位把主設備號和次設備號進行處理,生成一個32位的數據。調用register_chrdev_region()注冊設備號,linux驅動根據散列hash表來建立設備描述cdev結構體的索引,當hash表的index沖突時,采用鏈表的方式避免沖突,這樣可以通過設備號快速找到cdev結構體的地址。

4、調用cdev_init()來初始化結構體cdev,最重要的是將file_operations保存在cdev中,file_oerations里邊有本地實現的open release ioctl等具體功能的函數指針,這樣可以在使用驅動的時候找到相應的實現函數。

5、調用cdev_add()來添加設備結構體cdev到hash表中,根據參數設備號可以找到注冊設備號時在hash表中的位置,然后將cdev結構體地址添加進去

6、映射io端口,即映射io端口的物理地址為虛擬地址,因為在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定,可以在數據手冊中找到。但是CPU通常并沒有為這些已知的外設I/O內存資源的物理地址預定義虛擬地址范圍,驅動程序并不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內,然后才能根據映射所得到的核心虛地址范圍,通過訪問內存指令訪問這些I/O內存資源。

7、使用命令mknod 添加/dev目錄下設備描述文件,其實主要就是描述了我們輸入的三個參數,首先c代表字符型設備,500代表主設備號,0代表次設備號。三個參數的用法在下邊的流程描述。

8、調用rmmod命令后,卸載驅動,找到驅動中moudle_exit()宏來找到卸載驅動的退出函數,在這個里邊調用cdev_del()和unregister_chrdev_region()刪除設備并且去掉設備號的注冊,相反的過程,不用贅言。

驅動使用:

1、在驅動測試文件中,首先打開了/dev目錄下的設備文件,但是這個文件只是設備基本信息的描述,沒有實質的動作,具體的作用可以看作為設備的索引,通過打開文件可以找到設備驅動的位置。這里分析我們輸入的三個參數,c表示字符型設備open系統調用中拿到c就知道要去找字符型設備的結構體。主設備號和次設備號用來索引hash散列表,找到cdev結構體的地址,而cdev中保存有file_operations的地址,就可以找到驅動的具體實現函數,就是驅動加載的逆過程。而open執行完之后,返回一個文件描述符,這個描述符中就帶有找到的cdev地址。

2、Open函數根據得到的cdev找到file_operations 中的.open對應的的函數指針,調用這個函數來初始化驅動,這里可以做io端口控制寄存器的設置,將相關的端口設置為輸出。

3、調用ioctl通信,ioctl是io管道管理函數,是linux系統封裝用來給驅動用的通信函數,方便用戶使用,不用關心通信的實現方式,也不用考慮通信是否跨線程或者跨進程,可以看作是一個通道,在使用時塞入數據,在驅動里邊寫好拿出數據并做相應的處理及可以,感覺非常像socket套接字。

4、測試文件中調用ioctl傳入數據,ioctl根據傳入的文件描述符參數中的cdev找到file_operations 中的. unlocked_ioctl對應的的函數指針,在這個函數里邊調用copy_from_user(),可以取出傳入的參數,根據參數做驅動對應的動作。

5、退出驅動后,跟2-4同樣的道理找到.release執行,釋放驅動。

三、??? 代碼的理解

實驗過程中仔細研究了代碼的流程,感覺有幾點不足:

1、端口的控制寄存器和數據寄存器初始化放在驅動模塊加載的函數中,這從實際的情況來講并不合適。

2、io端口映射也放在驅動模塊加載的過程中,也不太合適,端口映射是在消耗系統資源,而加載之后一直不使用驅動時,端口映射一直存在系統中,只有在驅動卸載之后才釋放,從實際情況來講也不合適。

3、對于cdev結構體的聲明和定義放在驅動文件中有過疑惑,因為之前我以為cdev是一個鏈表,通過系統中的全局變量保存鏈表頭,然后根據設備號去索引鏈表,如果放在本地,發生意外修改了結構體中指向下一個結構體的指針,會導致鏈表斷裂,這是個很嚴重的問題。后來研究發現是用hash散列表實現的,故不存在這樣的問題。

4、測試文件中只有open函數,沒有調用close函數,當然這可以理解為需要在實驗過程中自己實現,比如不用ctl c退出而是用讀取輸入字符判斷退出函數,然后退出之前調用close。

四、和裸機平臺設備驅動的不同:

第一次接觸帶有操作系統的驅動編程,之前感覺系統驅動比較神秘的面紗也被揭開了,跟裸機平臺的設備驅動相比,區別就是

1、系統給驅動的編程增加了一個框架,需要依照系統對于驅動的統一管理來實現框架的內容,比如增加moudle_init,在init中注冊設備號 添加設備等, 這都是在告訴系統,我們寫的驅動具體實現的東西在哪里。

2、具體實際的動作,跟裸機驅動是一致的,因為驅動的本質就是直接操作硬件,而對于硬件的操作方法是硬件的數據手冊里邊統一定義的,手冊沒有改,對硬件的操作同樣不會變化,

3、系統實現控制驅動的通信方法,裸機驅動調用控制驅動的方法直接調用就可以,而系統中需要通過ioctl來實現應用驅動的程序跟驅動程序的通信控制。

源碼:

1、Makefile

ifeq ($(KERNELRELEASE),)


KERNELDIR ?= /home/linux/linux-3.14-fs4412/
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)


modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD)?


modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install


clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*


.PHONY: modules modules_install clean


else
? ? obj-m := fs4412_led.o

endif

2、頭文件

#ifndef S5pV210_LED_HH
#define S5pV210_LED_HH


#define LED_MAGIC 'L'
/*
?* need arg = 1/2?
?*/


#define LED_ON _IOW(LED_MAGIC, 0, int)
#define LED_OFF _IOW(LED_MAGIC, 1, int)

?

#endif

3、實現文件

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>


#include <asm/io.h>
#include <asm/uaccess.h>


#include "fs4412_led.h"


MODULE_LICENSE("Dual BSD/GPL");


#define LED_MA 500
#define LED_MI 0
#define LED_NUM 1


#define FS4412_GPF3CON 0x114001E0
#define FS4412_GPF3DAT 0x114001E4


#define FS4412_GPX1CON 0x11000C20
#define FS4412_GPX1DAT 0x11000C24


#define FS4412_GPX2CON 0x11000C40
#define FS4412_GPX2DAT 0x11000C44








static unsigned int *gpf3con;
static unsigned int *gpf3dat;


static unsigned int *gpx1con;
static unsigned int *gpx1dat;


static unsigned int *gpx2con;
static unsigned int *gpx2dat;




struct cdev cdev;


void fs4412_led_on(int nr)
{
switch(nr) {
case 1:?
writel(readl(gpx2dat) | 1 << 7, gpx2dat);
break;
case 2:?
writel(readl(gpx1dat) | 1 << 0, gpx1dat);
break;
case 3:?
writel(readl(gpf3dat) | 1 << 4, gpf3dat);
break;
case 4:?
writel(readl(gpf3dat) | 1 << 5, gpf3dat);
break;
}
}


void fs4412_led_off(int nr)
{
switch(nr) {
case 1:?
writel(readl(gpx2dat) & ~(1 << 7), gpx2dat);
break;
case 2:?
writel(readl(gpx1dat) & ~(1 << 0), gpx1dat);
break;
case 3:?
writel(readl(gpf3dat) & ~(1 << 4), gpf3dat);
break;
case 4:?
writel(readl(gpf3dat) & ~(1 << 5), gpf3dat);
break;
}
}


static int s5pv210_led_open(struct inode *inode, struct file *file)
{
return 0;
}

static int s5pv210_led_release(struct inode *inode, struct file *file)
{
return 0;
}

static long s5pv210_led_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int nr;


if(copy_from_user((void *)&nr, (void *)arg, sizeof(nr)))
return -EFAULT;


if (nr < 1 || nr > 4)
return -EINVAL;


switch (cmd) {
case LED_ON:
fs4412_led_on(nr);
break;
case LED_OFF:
fs4412_led_off(nr);
break;
default:
printk("Invalid argument");
return -EINVAL;
}


return 0;
}


int fs4412_led_ioremap(void)
{
int ret;


gpf3con = ioremap(FS4412_GPF3CON, 4);
if (gpf3con == NULL) {
printk("ioremap gpf3con\n");
ret = -ENOMEM;
return ret;
}


gpf3dat = ioremap(FS4412_GPF3DAT, 4);
if (gpf3dat == NULL) {
printk("ioremap gpx2dat\n");
ret = -ENOMEM;
return ret;
}




gpx1con = ioremap(FS4412_GPX1CON, 4);
if (gpx1con == NULL) {
printk("ioremap gpx2con\n");
ret = -ENOMEM;
return ret;
}


gpx1dat = ioremap(FS4412_GPX1DAT, 4);
if (gpx1dat == NULL) {
printk("ioremap gpx2dat\n");
ret = -ENOMEM;
return ret;
}
gpx2con = ioremap(FS4412_GPX2CON, 4);
if (gpx2con == NULL) {
printk("ioremap gpx2con\n");
ret = -ENOMEM;
return ret;
}


gpx2dat = ioremap(FS4412_GPX2DAT, 4);
if (gpx2dat == NULL) {
printk("ioremap gpx2dat\n");
ret = -ENOMEM;
return ret;
}


return 0;
}


void fs4412_led_iounmap(void)
{
iounmap(gpf3con);
iounmap(gpf3dat);
iounmap(gpx1con);
iounmap(gpx1dat);
iounmap(gpx2con);
iounmap(gpx2dat);
}


void fs4412_led_io_init(void)
{


writel((readl(gpf3con) & ~(0xff << 16)) | (0x11 << 16), gpf3con);
writel(readl(gpx2dat) & ~(0x3<<4), gpf3dat);


writel((readl(gpx1con) & ~(0xf << 0)) | (0x1 << 0), gpx1con);
writel(readl(gpx1dat) & ~(0x1<<0), gpx1dat);


writel((readl(gpx2con) & ~(0xf << 28)) | (0x1 << 28), gpx2con);
writel(readl(gpx2dat) & ~(0x1<<7), gpx2dat);
}

struct file_operations s5pv210_led_fops = {
.owner = THIS_MODULE,
.open = s5pv210_led_open,
.release = s5pv210_led_release,
.unlocked_ioctl = s5pv210_led_unlocked_ioctl,
};


static int s5pv210_led_init(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);? ? ? ?//構建設備號
int ret;


ret = register_chrdev_region(devno, LED_NUM, "newled");? //分配設備號 ,這里用靜態申請
if (ret < 0) {
printk("register_chrdev_region\n");
return ret;
}


cdev_init(&cdev, &s5pv210_led_fops);? ? ?//cdev結構體初始化,最關鍵的是將file_operations結構體的地址關聯到cdev中
cdev.owner = THIS_MODULE;
ret = cdev_add(&cdev, devno, LED_NUM);? ?//增加設備,將主設備號和次設備號關聯到cdev中
if (ret < 0) {
printk("cdev_add\n");
goto err1;
}


ret = fs4412_led_ioremap();? ? ? ? //映射io端口的物理地址為虛擬地址,因為般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常并沒有為這些已知的外設I/O內存資源的物理地址預定義虛擬地址范圍,驅動程序并不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然后才能根據映射所得到的核心虛地址范圍,通過訪問內存指令訪問這些I/O內存資源。
if (ret < 0)
goto err2;




fs4412_led_io_init();


printk("Led init\n");


return 0;
err2:
cdev_del(&cdev);
err1:
unregister_chrdev_region(devno, LED_NUM);? ? ?//注銷設備號
return ret;
}


static void s5pv210_led_exit(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);


fs4412_led_iounmap();
cdev_del(&cdev);
unregister_chrdev_region(devno, LED_NUM);
printk("Led exit\n");
}


module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

總結

以上是生活随笔為你收集整理的linux设备驱动的实现与理解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。