溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

發(fā)布時(shí)間:2021-10-22 11:08:23 來源:億速云 閱讀:127 作者:柒染 欄目:互聯(lián)網(wǎng)科技

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng),文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

這里介紹一下如何利用Linux驅(qū)動(dòng)模型來完成一個(gè)LED燈設(shè)備驅(qū)動(dòng)。點(diǎn)一個(gè)燈有什么好談呢?況且Linux下有專門的leds驅(qū)動(dòng)子系統(tǒng)。

點(diǎn)燈有啥好聊呢?

在很多嵌入式系統(tǒng)里,有可能需要實(shí)現(xiàn)數(shù)字開關(guān)量輸出,比如:

  • LED狀態(tài)顯示

  • 閥門/繼電器控制

  • 蜂鳴器

  • ......

嵌入式Linux一般需求千變?nèi)f化,也不可能這些需求都有現(xiàn)成設(shè)備驅(qū)動(dòng)代碼可供使用,所以如何學(xué)會(huì)完成一個(gè)開關(guān)量輸出設(shè)備的驅(qū)動(dòng),一方面點(diǎn)個(gè)燈可以比較快了解如何具體寫一個(gè)字符類設(shè)備驅(qū)動(dòng),另一方面實(shí)際項(xiàng)目中對(duì)于開關(guān)量輸出設(shè)備就可以這樣干,所以是具有較強(qiáng)的實(shí)用價(jià)值的。

要完成這樣一個(gè)開關(guān)量輸出GPIO的驅(qū)動(dòng)程序,需要梳理梳理下面這些概念:

  • 設(shè)備編號(hào)

  • 設(shè)備掛載

  • 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)

設(shè)備編號(hào)

字符設(shè)備是通過文件系統(tǒng)內(nèi)的設(shè)備名稱進(jìn)行訪問的,其本質(zhì)是設(shè)備文件系統(tǒng)樹的節(jié)點(diǎn)。故Linux下設(shè)備也是一個(gè)文件,Linux下字符設(shè)備在/dev目錄下??梢栽陂_發(fā)板的控制臺(tái)或者編譯的主Linux系統(tǒng)中利用ls -l /dev查看,如下圖:

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

對(duì)于ls -l列出的屬性,做一個(gè)比較細(xì)的解析:

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

細(xì)心的朋友或許會(huì)發(fā)現(xiàn)設(shè)備號(hào)屬性,在有的文件夾下列出來不是這樣,這就對(duì)了!普通文件夾下是這樣:

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

差別在于一個(gè)是文件大小,一個(gè)是設(shè)備號(hào)。

再細(xì)心一點(diǎn)的朋友或許還會(huì)問,這些/dev下的文件時(shí)間屬性為神馬都相差無幾?這是因?yàn)?dev設(shè)備樹節(jié)點(diǎn)是在內(nèi)核啟動(dòng)掛載設(shè)備驅(qū)動(dòng)動(dòng)態(tài)生成的,所以時(shí)間就是系統(tǒng)開機(jī)后按次序生成的,你如不信,不妨重啟一下系統(tǒng)在查看一下。

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

常見文件類型:

  • d: directory 文件夾

  • l: link  符號(hào)鏈接

  • p: FIFO pipe 管道文件,可以用mkfifo命令生成創(chuàng)建

  • s: socket 套接字文件

  • c: char 字符型設(shè)備文件

  • b: block 塊設(shè)備文件

  • -:常規(guī)文件

回到設(shè)備號(hào),設(shè)備號(hào)是一個(gè)32位無符號(hào)整型數(shù),其中:

  • 12位用來表示主設(shè)備號(hào),用于標(biāo)識(shí)設(shè)備對(duì)應(yīng)的驅(qū)動(dòng)程序。

  • 20位用來表示次設(shè)備號(hào),用于正確確定設(shè)備文件所指的設(shè)備。

這怎么理解呢,看下串口類設(shè)備就比較清楚了:

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

主設(shè)備號(hào)一樣證明這些設(shè)備共用了一個(gè)驅(qū)動(dòng)程序,而次設(shè)備號(hào)不一樣,則對(duì)應(yīng)了不同的串口設(shè)備。那么怎么得到設(shè)備號(hào)呢?

/*下列定義位于./include/linux/types.h */typedef u32 __kernel_dev_t;typedef __kernel_dev_t dev_t;/* 下面宏用于生成主設(shè)備號(hào),次設(shè)備號(hào)       *//* 下列定義位于./include/linux/Kdev_t.h */#define MINORBITS 20#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

使用舉例:

/* 主設(shè)備號(hào) */MAJOR(dev_t dev); /* 次設(shè)備號(hào) */MINOR(dev_t dev);

設(shè)備掛載

為簡化問題,本文描述一下動(dòng)態(tài)加載設(shè)備驅(qū)動(dòng)模塊,暫不考慮設(shè)備樹。參考<<Linux設(shè)備驅(qū)動(dòng)程序>>一書??蓞⒄涨拔膶Ⅱ?qū)動(dòng)編譯成模塊,然后利用下面腳步動(dòng)態(tài)加載模塊。由前面描述,知道設(shè)備最終需要在/dev目錄下生成一個(gè)設(shè)備文件,那么這個(gè)設(shè)備文件節(jié)點(diǎn)是怎么生成呢,看看下面的腳本:

#!/bin/sh#-----------------------------------------------------------------------module="led"
device="led"
mode="664"
group="staff"# 利用insmod命令加載設(shè)備模塊insmod -f $module.ko $* || exit 1# 獲取系統(tǒng)分配的主設(shè)備號(hào) major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"`# 刪除舊節(jié)點(diǎn)rm -f /dev/${device} #創(chuàng)建設(shè)備文件節(jié)點(diǎn)mknod /dev/${device} c $major 0#設(shè)置設(shè)備文件節(jié)點(diǎn)屬性chgrp $group /dev/${device}
chmod $mode  /dev/${device}

這里要提一下/proc/devices,這是一個(gè)文件記錄了字符和塊設(shè)備的主設(shè)備號(hào),以及分配到這些設(shè)備號(hào)的設(shè)備名稱。比如使用cat命令來列出這個(gè)文件內(nèi)容:

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

關(guān)鍵數(shù)據(jù)結(jié)構(gòu)

字符設(shè)備由什么關(guān)鍵數(shù)據(jù)結(jié)構(gòu)進(jìn)行抽象的呢,來看看:

  • file_operations定義在./include/linux/fs.h

  • cdev定義在./include/linux/cdev.h

如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)

cdev中與字符設(shè)備驅(qū)動(dòng)編程相關(guān)兩個(gè)數(shù)據(jù)域:

  • const struct file_operations *ops;

  • dev_t dev;設(shè)備編號(hào)

文件操作符是一個(gè)龐大的數(shù)據(jù)結(jié)構(gòu),常規(guī)字符設(shè)備驅(qū)動(dòng)一般需要實(shí)現(xiàn)下面一些函數(shù)指針:

  • read:用來實(shí)現(xiàn)從設(shè)備中讀取數(shù)據(jù)

  • write:用于實(shí)現(xiàn)寫入數(shù)據(jù)到設(shè)備

  • ioctl:實(shí)現(xiàn)執(zhí)行設(shè)備特定命令的方法

  • open:用實(shí)現(xiàn)打開一個(gè)設(shè)備文件

  • release:當(dāng)file結(jié)構(gòu)被釋放時(shí),將調(diào)用這個(gè)接口函數(shù)

點(diǎn)燈設(shè)備

先上代碼(可左右滑動(dòng)顯示):

#include <linux/module.h>#include <linux/init.h>#include <linux/errno.h>#include <linux/kernel.h>  /* printk() */#include <linux/major.h>#include <linux/cdev.h>#include <linux/fs.h>      /* everything... */#include <linux/gpio.h>#include <asm/uaccess.h>   /* copy_*_user *//*這里具體參考不同開發(fā)板的電路 GPIOC24 */#define LED_CTRL   (2*32+24)  static const unsigned int led_pad_cfg = LED_CTRL;struct t_led_dev{ struct cdev cdev; unsigned char value; 
};struct t_led_dev  led_dev;static dev_t led_major;static dev_t led_minor=0; static int led_open(struct inode * inode,struct file * filp){ 
 filp->private_data = &led_dev; 
 
 printk ("led is opened!\n");          
 return 0;
}static int led_release(struct inode * inode,
                       struct file * filp){
 return 0;
}static ssize_t led_read(struct file * file, 
                        char __user * buf,
                        size_t count, 
                        loff_t *ppos){
 ssize_t ret=1;   
 if(copy_to_user(&(led_dev.value),buf,1))
  return -EFAULT;
 printk ("led is read!\n");
 return ret;
}static ssize_t led_write(struct file * filp, 
                         const char __user *buf,
                         size_t count,loff_t *ppos){
 unsigned char value; 
    ssize_t retval = 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");
 return retval;
}static const struct file_operations led_fops = { .owner = THIS_MODULE,
 .read  = led_read,
 .write = led_write,
 .open  = led_open,
 .release = led_release,
};static void led_setup_cdev(struct t_led_dev * dev, int index){ 
    /* 初始化字符設(shè)備驅(qū)動(dòng)數(shù)據(jù)域 */ int err,devno = MKDEV(led_major,led_minor+index);
 cdev_init(&(dev->cdev),&led_fops);
 dev->cdev.owner = THIS_MODULE;
 dev->cdev.ops = &led_fops;
    /* 字符設(shè)備注冊(cè) */ err = cdev_add(&(dev->cdev),devno,1);
 if(err)
  printk(KERN_NOTICE "Error %d adding led %d",err,index); 
}static int led_gpio_init(void){
 if (gpio_request(LED_CTRL, "led") < 0) {
  printk("Led request gpio failed\n");
  return -1;
 }
 
 printk("Led gpio requested ok\n");
 
 gpio_direction_output(LED_CTRL, 1); 
 gpio_set_value(LED_CTRL, 1);
 
 return 0;
}/* 注銷設(shè)備 */void led_cleanup(void){ 
 dev_t devno = 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);    //注銷設(shè)備號(hào)  }/* 注冊(cè)設(shè)備 */static int led_init(void){ 
 int result;
 dev_t dev = MKDEV( led_major, 0 );
 /* 動(dòng)態(tài)分配設(shè)備號(hào) */ result = alloc_chrdev_region(&dev, 0, 1, "led");      
 if(result<0)
  return result; 
 
 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"); 
 
 return result;
}

module_init(led_init);
module_exit(led_cleanup);

MODULE_DESCRIPTION("Led device demo");
MODULE_AUTHOR("embinn");
MODULE_LICENSE("GPL");

來總結(jié)一下要點(diǎn):

  • init函數(shù),需要用module_init宏包起來,本例中即為led_init,module_init宏的作用就是選編譯為模塊或進(jìn)內(nèi)核的底層實(shí)現(xiàn),建議剛開始不必深究。一般而言主要實(shí)現(xiàn):

    • 申請(qǐng)分配主設(shè)備號(hào)alloc_chrdev_region

    • 為特定設(shè)備相關(guān)數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存

    • 將入口函數(shù)(open read write等)與字符設(shè)備驅(qū)動(dòng)的cdev抽象數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)

    • 將主設(shè)備與驅(qū)動(dòng)程序cdev相關(guān)聯(lián)

    • 申請(qǐng)硬件資源,初始化硬件

    • 調(diào)用cdev_add注冊(cè)設(shè)備

  • exit函數(shù),一樣需要用module_exit包起來,主要負(fù)責(zé):

    • 釋放硬件資源

    • 調(diào)用cdev_del刪除設(shè)備

    • 調(diào)用unregister_chrdev_region注銷設(shè)備號(hào)

  • 用戶空間與驅(qū)動(dòng)數(shù)據(jù)交換

    • copy_to_user,如其名一樣,將內(nèi)核空間數(shù)據(jù)信息傳遞到用戶空間

    • copy_from_user,如其名一樣,從用戶空間拷貝數(shù)據(jù)進(jìn)內(nèi)核空間

  • 善用printk進(jìn)行驅(qū)動(dòng)調(diào)試,這是內(nèi)核打印函數(shù)。

  • gpio相關(guān)操作函數(shù),這里就不一一列舉其作用了,比較容易理解。

測試驅(qū)動(dòng)

#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#define READ_SIZE 10int main(int argc, char **argv){
 int fd,count;
 float value;
 unsigned char buf[READ_SIZE+1];
 printf( "Cmd argv[0]:%s,argv[1]:%s,argv[2]:%s\n",argv[0],argv[1],argv[2] );
 
 if( argc<2 ){
  printf( "[Usage: test device_name ]\n" );
  exit(0);
 }
    if(strlen(argv[2]!=1)
        printf( "Invalid parameter\n" );
    
 if(( fd = open(argv[1],O_WRONLY  ))<0){
  printf( "Error:can not open the device: %s\n",argv[1] );
  exit(1);
 }
    
    if(argv[2][0] == '1')
        buf[0] = 1;
    else if(argv[2][0] == '0')
        buf[0] = 0;
    else        printf( "Invalid parameter\n" );

 printf("write: %d\n",buf[0]);
 if( (count = write( fd, buf ,1 ))<0 ){
  perror("write error.\n");
  exit(1);  
 }
 
 close(fd);
 printf("close device %s\n",argv[1] );
 return 0;
}

編譯成可執(zhí)行文件,調(diào)用前面的腳本加載設(shè)備后,在/dev下就可以看到led設(shè)備了。比如測試代碼編譯成ledTest執(zhí)行文件,則使用下面命令運(yùn)行測試程序就可以看到led控制效果了:

/*打開led 具體取決電路是高有效還是低有效*/./ledTest /dev/led 1./ledTest /dev/led 0

這樣就實(shí)現(xiàn)了用戶空間驅(qū)動(dòng)底層設(shè)備了,實(shí)際應(yīng)用代碼就可以這樣去訪問底層的字符型設(shè)備。

最后總結(jié)了簡單字符設(shè)備的驅(qū)動(dòng)開發(fā)的一些要點(diǎn),以及如何動(dòng)態(tài)加載,在設(shè)備文件系統(tǒng)樹上創(chuàng)建設(shè)備節(jié)點(diǎn),并演示了驅(qū)動(dòng)以及驅(qū)動(dòng)使用的基本要點(diǎn)。

上述就是小編為大家分享的如何從點(diǎn)一個(gè)燈開始學(xué)寫Linux字符設(shè)備驅(qū)動(dòng)了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI