linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!
原標題:從點一個燈開始學寫Linux字符設備驅動!
[導讀] 前一篇文章,介紹了如何將一個hello word模塊編譯進內核或者編譯為動態加載內核模塊,本篇來介紹一下如何利用Linux驅動模型來完成一個LED燈設備驅動。點一個燈有什么好談呢?況且Linux下有專門的leds驅動子系統。
點燈有啥好聊呢?
在很多嵌入式系統里,有可能需要實現數字開關量輸出,比如:
LED狀態顯示
閥門/繼電器控制
蜂鳴器
......
嵌入式Linux一般需求千變萬化,也不可能這些需求都有現成設備驅動代碼可供使用,所以如何學會完成一個開關量輸出設備的驅動,一方面點個燈可以比較快了解如何具體寫一個字符類設備驅動,另一方面實際項目中對于開關量輸出設備就可以這樣干,所以是具有較強的實用價值的。
要完成這樣一個開關量輸出GPIO的驅動程序,需要梳理梳理下面這些概念:
設備編號
設備掛載
關鍵數據結構
設備編號
字符設備是通過文件系統內的設備名稱進行訪問的,其本質是設備文件系統樹的節點。故Linux下設備也是一個文件,Linux下字符設備在/dev目錄下。可以在開發板的控制臺或者編譯的主Linux系統中利用ls -l /dev查看,如下圖:
對于ls -l列出的屬性,做一個比較細的解析:
細心的朋友或許會發現設備號屬性,在有的文件夾下列出來不是這樣,這就對了!普通文件夾下是這樣:
差別在于一個是文件大小,一個是設備號。
再細心一點的朋友或許還會問,這些/dev下的文件時間屬性為神馬都相差無幾?這是因為/dev設備樹節點是在內核啟動掛載設備驅動動態生成的,所以時間就是系統開機后按次序生成的,你如不信,不妨重啟一下系統在查看一下。
常見文件類型:
d: directory 文件夾
l: link 符號鏈接
p: FIFO pipe 管道文件,可以用mkfifo命令生成創建
s: socket 套接字文件
c: char 字符型設備文件
b: block 塊設備文件
-:常規文件
回到設備號,設備號是一個32位無符號整型數,其中:
12位用來表示主設備號,用于標識設備對應的驅動程序。
20位用來表示次設備號,用于正確確定設備文件所指的設備。
這怎么理解呢,看下串口類設備就比較清楚了:
主設備號一樣證明這些設備共用了一個驅動程序,而次設備號不一樣,則對應了不同的串口設備。那么怎么得到設備號呢?
/*下列定義位于./include/linux/types.h */
typedefu32 __kernel_dev_t;
typedef__kernel_dev_tdev_t;
/* 下面宏用于生成主設備號,次設備號 */
/* 下列定義位于./include/linux/Kdev_t.h */
# defineMINORBITS 20
# defineMINORMASK ((1U << MINORBITS) - 1)
# defineMAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
# defineMINOR(dev) ((unsigned int) ((dev) & MINORMASK))
# defineMKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
使用舉例:
/* 主設備號 */
MAJOR( dev_tdev);
/* 次設備號 */
MINOR( dev_tdev);
設備掛載
為簡化問題,本文描述一下動態加載設備驅動模塊,暫不考慮設備樹。參考<>一書。可參照前文將驅動編譯成模塊,然后利用下面腳步動態加載模塊。由前面描述,知道設備最終需要在/dev目錄下生成一個設備文件,那么這個設備文件節點是怎么生成呢,看看下面的腳本:
#!/bin/sh
#-----------------------------------------------------------------------
module="led"
device="led"
mode="664"
group="staff"
# 利用insmod命令加載設備模塊
insmod -f $module.ko $* || exit 1
#獲取系統分配的主設備號
major=`cat /proc/devices | awk "$2=="$module" {print $1}"`
# 刪除舊節點
rm -f /dev/${device}
# 創建設備文件節點
mknod /dev/${device} c $major 0
#設置設備文件節點屬性
chgrp $group /dev/${device}
chmod $mode /dev/${device}
這里要提一下/proc/devices,這是一個文件記錄了字符和塊設備的主設備號,以及分配到這些設備號的設備名稱。比如使用cat命令來列出這個文件內容:
關鍵數據結構
字符設備由什么關鍵數據結構進行抽象的呢,來看看:
file_operations定義在./include/linux/fs.h
cdev定義在./include/linux/cdev.h
cdev中與字符設備驅動編程相關兩個數據域:
const struct file_operations *ops;
dev_t dev;設備編號
文件操作符是一個龐大的數據結構,常規字符設備驅動一般需要實現下面一些函數指針:
read:用來實現從設備中讀取數據
write:用于實現寫入數據到設備
ioctl:實現執行設備特定命令的方法
open:用實現打開一個設備文件
release:當file結構被釋放時,將調用這個接口函數點燈設備
先上代碼(可左右滑動顯示):
# include
# include
# include
# include /* printk */
# include
# include
# include /* everything... */
# include
# include /* copy_*_user */
/*這里具體參考不同開發板的電路 GPIOC24 */
# defineLED_CTRL (2*32+24)
staticconstunsignedintled_pad_cfg = LED_CTRL;
structt_led_dev{
structcdevcdev;
unsignedcharvalue;
};
structt_led_devled_dev;
staticdev_tled_major;
staticdev_tled_minor= 0;
staticintled_open(struct inode * inode,struct file * filp)
{
filp->private_data = &led_dev;
printk ( "led is opened!n");
return0;
}
staticintled_release(struct inode * inode,
struct file * filp)
{
return0;
}
staticssize_t led_read(struct file * file,
char__user * buf,
size_tcount,
loff_t*ppos)
{
ssize_tret= 1;
if(copy_to_user(&(led_dev.value),buf, 1))
return-EFAULT;
printk ( "led is read!n");
returnret;
}
staticssize_t led_write(struct file * filp,
constchar__user *buf,
size_tcount, loff_t*ppos)
{
unsignedcharvalue;
ssize_tretval = 0;
if(copy_from_user(&value,buf, 1))
return-EFAULT;
if(value& 0x01)
gpio_set_value(led_pad_cfg, 1);
else
gpio_set_value(led_pad_cfg, 0);
printk ( "led is written!n");
returnretval;
}
staticconststructfile_operationsled_fops= {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write,
.open = led_open,
.release = led_release,
};
staticvoidled_setup_cdev(struct t_led_dev * dev, intindex)
{
/* 初始化字符設備驅動數據域 */
interr,devno = MKDEV(led_major,led_minor+index);
cdev_init(&(dev->cdev),&led_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &led_fops;
/* 字符設備注冊 */
err = cdev_add(&(dev->cdev),devno, 1);
if(err)
printk(KERN_NOTICE "Error %d adding led %d",err,index);
}
staticintled_gpio_init( void)
{
if(gpio_request(LED_CTRL, "led") < 0) {
printk( "Led request gpio failedn");
return-1;
}
printk( "Led gpio requested okn");
gpio_direction_output(LED_CTRL, 1);
gpio_set_value(LED_CTRL, 1);
return0;
}
/* 注銷設備 */
voidled_cleanup( void)
{
dev_tdevno = MKDEV(led_major, led_minor);
gpio_set_value(LED_CTRL, 0);
gpio_free(LED_CTRL);
cdev_del(&led_dev.cdev);
unregister_chrdev_region(devno, 1); //注銷設備號
}
/* 注冊設備 */
staticintled_init( void)
{
intresult;
dev_tdev = MKDEV( led_major, 0);
/* 動態分配設備號 */
result = alloc_chrdev_region(&dev, 0, 1, "led");
if(result< 0)
returnresult;
led_major = MAJOR(dev);
memset(&led_dev, 0, sizeof(struct t_led_dev));
led_setup_cdev(&led_dev, 0);
led_gpio_init;
printk ( "led device initialised!n");
returnresult;
}
module_init(led_init);
module_exit(led_cleanup);
MODULE_DEION( "Led device demo");
MODULE_AUTHOR( "embinn");
MODULE_LICENSE( "GPL");
來總結一下要點:
init函數,需要用module_init宏包起來,本例中即為led_init,module_init宏的作用就是選編譯為模塊或進內核的底層實現,建議剛開始不必深究。一般而言主要實現:
申請分配主設備號alloc_chrdev_region
為特定設備相關數據結構分配內存
將入口函數(open read write等)與字符設備驅動的cdev抽象數據結構關聯
將主設備與驅動程序cdev相關聯
申請硬件資源,初始化硬件
調用cdev_add注冊設備
exit函數,一樣需要用module_exit包起來,主要負責:
釋放硬件資源
調用cdev_del刪除設備
調用unregister_chrdev_region注銷設備號
用戶空間與驅動數據交換
copy_to_user,如其名一樣,將內核空間數據信息傳遞到用戶空間
copy_from_user,如其名一樣,從用戶空間拷貝數據進內核空間
善用printk進行驅動調試,這是內核打印函數。
gpio相關操作函數,這里就不一一列舉其作用了,比較容易理解。測試驅動# include
# include
# include
# include
# defineREAD_SIZE 10
intmain( intargc, char**argv){
intfd,count;
floatvalue;
unsignedcharbuf[READ_SIZE+ 1];
printf( "Cmd argv[0]:%s,argv[1]:%s,argv[2]:%sn",argv[ 0],argv[ 1],argv[ 2] );
if( argc< 2){
printf( "[Usage: test device_name ]n");
exit( 0);
}
if( strlen(argv[ 2]!= 1)
printf( "Invalid parametern");
if(( fd = open(argv[ 1],O_WRONLY ))< 0){
printf( "Error:can not open the device: %sn",argv[ 1] );
exit( 1);
}
if(argv[ 2][ 0] == '1')
buf[ 0] = 1;
elseif(argv[ 2][ 0] == '0')
buf[ 0] = 0;
else
printf( "Invalid parametern");
printf( "write: %dn",buf[ 0]);
if( (count = write( fd, buf , 1))< 0){
perror( "write error.n");
exit( 1);
}
close(fd);
printf( "close device %sn",argv[ 1] );
return0;
}
編譯成可執行文件,調用前面的腳本加載設備后,在/dev下就可以看到led設備了。 比如測試代碼編譯成ledTest執行文件,則使用下面命令運行測試程序就可以看到led控制效果了:
/*打開led 具體取決電路是高有效還是低有效*/
./ledTest /dev/led 1
./ledTest /dev/led 0
這樣就實現了用戶空間驅動底層設備了,實際應用代碼就可以這樣去訪問底層的字符型設備。
總結一下
本文總結了簡單字符設備的驅動開發的一些要點,以及如何動態加載,在設備文件系統樹上創建設備節點,并演示了驅動以及驅動使用的基本要點。
免責聲明:本文系網絡轉載,版權歸原作者所有。如涉及作品版權問題,請與我們聯系,我們將根據您提供的版權證明材料確認版權并支付稿酬或者刪除內容。返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html window 属性,html中
- 下一篇: 苹果怎么关闭系统自动更新_你经过我的同意