字符設備驅動 —— 規范編寫字符設備驅動


目錄:

  1、驅動與應用程序的設計思想

  2、字符設備驅動編寫步驟與規范

  3、操作寄存器地址:readl / writel

  4、實例:實現LED燈閃爍

 

1、驅動與應用程序的設計思想

用戶態:應用程序
        玩策略: 怎么去做
                1, 一閃一閃
                2,10s閃一次,也可以1s閃一次
                3,一直亮
                4,跑馬燈
        控制權是在應用程序(程序員)
--------------------------------------
內核態:驅動
        玩機制: 能做什么 
                led:亮 和 滅

 

2、字符設備驅動編寫步驟與規范

  1)步驟

    1,實現模塊加載和卸載入口函數
            module_init(chr_dev_init);
            module_exit(chr_dev_exit);
    
    2,在模塊加載入口函數中
        a,申請主設備號  (內核中用於區分和管理不同字符設備)
                 register_chrdev(dev_major, "chr_dev_test", &my_fops);

        b,創建設備節點文件 (為用戶提供一個可操作到文件接口--open())
                struct  class *class_create(THIS_MODULE, "chr_cls");
                struct  device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

        c, 硬件的初始化
               1,地址的映射
                    gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
               2,中斷到申請
               3,實現硬件的寄存器的初始化
                    // 需要配置gpio功能為輸出
                    *gpx2conf &= ~(0xf<<28);
                    *gpx2conf |= (0x1<<28);
        e,實現file_operations
                const struct file_operations my_fops = {
                        .open = chr_drv_open,
                        .read = chr_drv_read,
                        .write = chr_drv_write,
                        .release = chr_drv_close,
                };

  2)規范

    1,面向對象編程思想
        用一個結構體來表示一個對象

        //設計一個類型,描述一個設備的信息
        struct led_desc{
            unsigned int dev_major; //設備號
            struct class *cls;
            struct device *dev; //創建設備文件
            void *reg_virt_base;
        };

        struct led_desc *led_dev;//表示一個全局的設備對象

        
        // 0(在init中第一步做), 實例化全局的設備對象--分配空間
        //  GFP_KERNEL 如果當前內存不夠用到時候,該函數會一直阻塞(休眠)
        //  #include <linux/slab.h>
        led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
        if(led_dev == NULL)
        {
            printk(KERN_ERR "malloc error\n");
            return -ENOMEM;
        }

        led_dev->dev_major = 250;

    2,做出錯處理
            在某個位置出錯了,要將之前申請到資源進行釋放
    
        led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);    
    
        led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
        if(led_dev->dev_major < 0)
        {
            printk(KERN_ERR "register_chrdev error\n");
            ret = -ENODEV;
            goto err_0;
        }


        err_0:
            kfree(led_dev);
            return ret;

引用:我們知道內核有些函數是返回指針的,如Kmalloc分配內存,如果分配不到內核就會返回NULL指針,這樣我們可以通過判斷是否是NULL指針來判斷Kmalloc執行成功與否。但是有些函數返回錯誤時,我們不僅需要知道函數錯了,還需要知道錯在哪里了,也就是說我們要或得錯誤碼。在用戶空間編程的時候,因為每個線程都有一個error變量,我們可以通過訪問這個變量來得到錯誤碼。但是在內核就沒有這個變量,所以不能這樣或得函數執行的錯誤碼。內核開發者就根據內核的地址空間特點用了一種新的方法來或得錯誤碼,那就是PTR_ERR,ERR_PTR,IS_ERR這三個宏 

對錯誤處理的詳解見:解讀PTR_ERR,ERR_PTR,IS_ERR

 

3、操作寄存器地址的方式:readl / writel

  1)傳統的方式

  volatile unsigned long *gpxcon;
  *gpxcon &= ~(0xf<<28);

 

  2)內核提供的方式

readl/writel();
    u32 readl(const volatile void __iomem *addr) //從地址中讀取地址空間的值
   //都是讀取/寫入32位數據,即4字節
void writel(unsigned long value , const volatile void __iomem *addr) // 將value的值寫入到addr地址 例子1: // gpio的輸出功能的配置 u32 value = readl(led_dev->reg_virt_base); value &= ~(0xf<<28); value |= (0x1<<28); writel(value, led_dev->reg_virt_bas); 例子2: *gpx2dat |= (1<<7); 替換成: writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 );

實際上傳統方法和內核提供的方式並無差別,甚至內核的方式還有點繁雜,但是為了標准化與可移植性,推薦使用內核提供的方法來實現相應功能。

  4、實例:實現led燈閃爍

  1)led_drv.c

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/fs.h>
  4 #include <linux/device.h>
  5 #include <asm/uaccess.h>
  6 #include <asm/io.h>
  7 #include <linux/slab.h>
  8 
  9 //設計一個類型,描述一個設備的信息
 10 struct led_desc{
 11     unsigned int dev_major;  //設備號
 12     struct class *cls;   //創建設備文件的類
 13     struct device *dev;  //創建設備文件
 14     void *reg_virt_base; //寄存器地址的基准值, void *型,地址偏移時+4
 15 };
 16 
 17 //物理地址
 18 #define GPX2CON 0x11000C40
 19 #define GPX2_SIZE 8
 20 
 21 
 22 struct led_desc *led_dev;  //聲明全局的設備對象
 23 
 24 static int kernal_val = 555; 
 25 
 26 // 用戶空間向內核空間傳數據
 27 ssize_t led_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
 28 {
 29         
 30     int ret;
 31     int value;  //從用戶空間獲取到的數據存在value中
 32     ret = copy_from_user(&value, buf, count);
 33     if(ret > 0)
 34     {
 35         printk("copy_from_user");
 36         return -EFAULT;
 37     }
 38 
 39     if(value)  //根據用戶傳遞的值決定燈的亮滅
 40     {
 41          writel((readl(led_dev->reg_virt_base+4) | (0x1<<7)), led_dev->reg_virt_base+4);
 42 
 43     }else
 44         {
 45          writel((readl(led_dev->reg_virt_base+4) & ~(0x1<<7)), led_dev->reg_virt_base+4);
 46     }
 47     
 48     return 0;
 49 }
 50 
 51 // read(fd, buf, size)  用戶空間獲取內核空間的數據
 52 ssize_t led_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fops)
 53 {
 54     printk("---------read %s-------------\n",__FUNCTION__);
 55 
 56     int ret;
 57     ret = copy_to_user(buf, &kernal_val, count);
 58     if(ret > 0)
 59     {
 60         printk("copy_to_user");
 61         return -EFAULT;
 62     }
 63     
 64     return 0;
 65 }
 66 
 67 int led_drv_open (struct inode *inode, struct file *filp)
 68 {
 69     printk("---------open %s-------------\n",__FUNCTION__);
 70     
 71     return 0;
 72 }
 73 
 74 int led_drv_close (struct inode *inode, struct file *filp)
 75 {
 76     printk("---------close %s-------------\n",__FUNCTION__);
 77     
 78     return 0;
 79 }
 80 
 81 
 82 
 83 const struct file_operations my_fops = {
 84     .open    = led_drv_open,
 85     .read    = led_drv_read,
 86     .write   = led_drv_write,
 87     .release = led_drv_close,
 88 };
 89 
 90 static int __init led_dev_init(void)
 91 {
 92 
 93     int ret;
 94     
 95     //1、實例化全局的設備對象 -- 分配空間
 96     //GFP_KERNEL 若當前內存不夠用,該函數會一直阻塞
 97     led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
 98     if(led_dev == NULL)
 99     {
100         printk(KERN_ERR "malloc error\n");
101         return -ENOMEM;            
102     }
103 
104     //裝載一般都是申請設備號資源
105     //2、動態申請設備號
106     led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
107     if(led_dev->dev_major < 0)
108     {
109         printk(KERN_ERR "register_led_dev error\n");
110         ret = -ENODEV;
111         goto err_0;
112         //return -ENODEV;  //如果直接返回的話,kmalloc分配的空間不會釋放,造成內存泄漏
113     }
114 
115     //3、自動創建設備節點
116     led_dev->cls = class_create(THIS_MODULE, "led_cls");
117     
118     if(IS_ERR(led_dev->cls))
119     {
120         printk(KERN_ERR"class create error\n");
121         ret = PTR_ERR(led_dev->cls);  //將指針出錯的具體原因轉換成一個出錯碼
122         goto err_1;   //此步出錯不僅要釋放kmalloc空間,還要注銷注冊成功的設備號
123     }
124     
125     led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major,0),NULL,"led%d",0); //后兩參數:->led0
126 
127     if(IS_ERR(led_dev->dev))
128     {
129         printk(KERN_ERR"device create error\n");
130         ret = PTR_ERR(led_dev->dev);  //將指針出錯的具體原因轉換成一個出錯碼
131         goto err_2;   //此步出錯不僅要釋放kmalloc空間,還要注銷注冊成功的設備號
132     }
133 
134     //4、硬件初始化
135     //對地址進行映射
136     led_dev->reg_virt_base = ioremap(GPX2CON, GPX2_SIZE);  //映射兩個寄存器
137     if(led_dev->reg_virt_base == NULL)
138     {
139         printk(KERN_ERR"ioremap error\n");
140         ret = -ENOMEM;   
141         goto err_3;
142     }
143     
144     //GPIO輸出功能的配置,使用標准寫法
145     u32 value = readl(led_dev->reg_virt_base);
146     value &= ~(0xf << 28);
147     value |=  (0x1 << 28);
148     writel(value, led_dev->reg_virt_base);
149     
150     return 0;
151 
152 //出錯處理
153 err_3:
154     device_destroy(led_dev->cls, MKDEV(led_dev->dev_major,0));
155 
156 err_2:
157     class_destroy(led_dev->cls);
158     
159 err_1:
160     unregister_chrdev(led_dev->dev_major, "led_dev_test");
161     //err1放在err0前面,標簽會順序執行,err_1 -> err_0,進而釋放kmalloc的資源
162 
163 err_0:
164     kfree(led_dev);
165     return ret;   //釋放完空間后,再返回錯誤碼
166 
167 }
168 
169 static void __exit led_dev_exit(void)
170 {
171      //卸載一般都是釋放資源
172     iounmap(gpx2conf);
173     
174     //銷毀節點和類
175     device_destroy(led_dev->cls,MKDEV(led_dev->dev_major,0));
176     class_destroy(led_dev->cls);
177     //釋放設備號
178      unregister_chrdev(led_dev->dev_major, "led_dev_test");
179     kfree(led_dev);
180 }
181 
182 module_init(led_dev_init);
183 module_exit(led_dev_exit);
184 MODULE_LICENSE("GPL");
led_chr.c

  2) led_test.c

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/stat.h>
 7 #include <fcntl.h>
 8 
 9 
10 int main(int argc, char * argv [ ])
11 {
12     //調用驅動
13     int fd;
14     int value = 0;  //buf
15 
16     fd = open("/dev/led0", O_RDWR);  //設備文件名在驅動中創建設備文件時指定
17     if(fd < 0)
18     {
19         perror("open");
20         exit(1);
21     }
22 
23     read(fd, &value, 4);   //應用程序從內核獲取4字節數據存放在value中 
24     printf("__USER__: value = %d\n", value);  //打印獲取的值
25     sleep(1);               //sleep1秒,防止串口打印信息不完整
26 
27     //應用程序控制燈
28     while(1)
29     {
30         value = 0;
31         write(fd, &value, 4);
32         sleep(1);
33         
34         value = 1;
35         write(fd, &value, 4);
36         sleep(1);
37     }
38     
39 
40     close(fd);
41     
42     return 0;
43 }
led_test.c

  3) Makefile

 1 ROOTFS_DIR = /home/linux/source/rootfs#根文件系統路徑
 2 
 3 APP_NAME = chr_test
 4 MODULE_NAME = chr_drv1
 5 
 6 CROSS_COMPILE = /home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi-
 7 CC = $(CROSS_COMPILE)gcc
 8 
 9 ifeq ($(KERNELRELEASE),)
10 
11 KERNEL_DIR = /home/linux/kernel/linux-3.14-fs4412          #編譯過的內核源碼的路徑
12 CUR_DIR = $(shell pwd)     #當前路徑
13 
14 all:
15     make -C $(KERNEL_DIR) M=$(CUR_DIR) modules  #把當前路徑編成modules
16     $(CC) $(APP_NAME).c -o $(APP_NAME)
17     @#make -C 進入到內核路徑
18     @#M 指定當前路徑(模塊位置)
19 
20 clean:
21     make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
22 
23 install:
24     sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module     #把當前的所有.ko文件考到根文件系統的drv_module目錄
25 
26 else
27 
28 obj-m += MODULE_NAME    #指定內核要把哪個文件編譯成ko
29 
30 endif
Makefile

  實驗結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM