fl2440字符设备led驱动

首先要明白字符设备驱动注册的基本流程

当我们调用insomd命令加载驱动后,驱动程序从module_init函数开始执行:硬件初始化 -> 申请主次设备号 -> 定义fops(file_operations)结构体 -> 申请cdev结构体并把fops结构体嵌入cdev结构体中与之绑定 -> cdev字符设备的注册。

有一点需要明确的是,在Linux内核中,所有的设备都是以文件。我们对设备的操作即是对Linux内核中文件的操作。设备都在/dev目录下。而inode则是设备索引节点,每个文件产生后都会有相应的inode来标示。

驱动加载完成后,file_operations结构体中的成员可以为应用程序提供对设备进行各种操作的函数指针:open,read,write等。

cdev结构体是用来描述字符设备的,内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:

1 linux-2.6.22/include/linux/cdev.h
2 struct cdev {
3    struct kobject kobj;          // 每个 cdev 都是一个 kobject
4    struct module *owner;       // 指向实现驱动的模块
5    const struct file_operations *ops;   // 操纵这个字符设备文件的方法
6    struct list_head list;       // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
7    dev_t dev;                   // 起始设备编号
8    unsigned int count;       // 设备范围号大小
9 };

一个 cdev 一般它有两种定义初始化方式:静态的和动态的。

静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;

动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

s3c_led代码:

  1 /*********************************************************************************
  2  *      Copyright:  (C) 2012 Guo Wenxue<guowenxue@gmail.com>  
  3  *                  All rights reserved.
  4  *
  5  *       Filename:  s3c_led.c
  6  *    Description:  This file 
  7  *                 
  8  *        Version:  1.0.0(07/26/2012~)
  9  *         Author:  Guo Wenxue <guowenxue@gmail.com>
 10  *      ChangeLog:  1, Release initial version on "07/26/2012 10:03:40 PM"
 11  *                 
 12  ********************************************************************************/
 13 
 14 #include <linux/module.h>   /* Every Linux kernel module must include this head */
 15 #include <linux/init.h>     /* Every Linux kernel module must include this head */
 16 #include <linux/kernel.h>   /* printk() */
 17 #include <linux/fs.h>       /* struct fops */
 18 #include <linux/errno.h>    /* error codes */
 19 #include <linux/cdev.h>     /* cdev_alloc()  */
 20 #include <asm/io.h>         /* ioremap()  */
 21 #include <linux/ioport.h>   /* request_mem_region() */
 22 
 23 #include <asm/ioctl.h>      /* Linux kernel space head file for macro _IO() to generate ioctl command  */
 24 #ifndef __KERNEL__
 25 #include <sys/ioctl.h>      /* User space head file for macro _IO() to generate ioctl command */
 26 #endif
 27 //#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */
 28 
 29 
 30 #define DRV_AUTHOR                "Guo Wenxue <guowenxue@gmail.com>"
 31 #define DRV_DESC                  "S3C24XX LED driver"
 32 
 33 #define DEV_NAME                  "led"                                //定义设备名称
 34 #define LED_NUM                   4                                    //定义设备数量
 35 
 36 /* Set the LED dev major number */
 37 //#define LED_MAJOR                 79                        
 38 #ifndef LED_MAJOR        
 39 #define LED_MAJOR                 0        //定义默认的设备号为0,一般这个设备号是不可用的,但是这位自动分配主设备号的逻辑提供了方便
 40 #endif
 41 
 42 #define DRV_MAJOR_VER             1                                    
 43 #define DRV_MINOR_VER             0
 44 #define DRV_REVER_VER             0
 45 
 46 #define DISABLE                   0                                    //禁用某个特性的宏
 47 #define ENABLE                    1                                    //使能某个特性的宏
 48 
 49 #define GPIO_INPUT                0x00                                //定义GPIO输入模式用0x00代替
 50 #define GPIO_OUTPUT               0x01                                //定义GPIO输出模式用0x01代替
 51 
 52 
 53 #define PLATDRV_MAGIC             0x60                                //定义一个魔术字
 54 //魔术字有着特殊的功能,定义一个系统未用的魔术字,然后让魔术字生成我们定义的LED_OFF与LED_ON,这样我们的定义就不会和系统中别的宏定义相同了
 55 #define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)            
 56 #define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)
 57 
 58 #define S3C_GPB_BASE              0x56000010                        //定义GPB管脚控制寄存器的基址地址
 59 
 60 #define GPBCON_OFFSET             0                            //定义GPBCON的偏移地址,用来选定引脚并设置输入输出模式
 61 //GPBDAT用于读写引脚数据。输入模式时,读此寄存器可知相应引脚的电平是低还是高;输出模式写此引脚可设置输出高低电平
 62 #define GPBDAT_OFFSET             4                                    //定义GPBDAT的偏移地址
 63 /*GPBUP的某位为1时,相应引脚无内部上拉电阻;为0时相应引脚使用内部上拉电阻。
 64 上拉电阻的作用是当GPIO引脚出去第三态时,即既不是输出高电平也不是输出低电平,
 65 而是呈现高阻态,相当于没有接芯片。它的电平状态由上拉电阻下拉电阻决定。*/
 66 #define GPBUP_OFFSET              8
 67 #define S3C_GPB_LEN               0x10        /* 0x56000010~0x56000020  */        //GPB寄存器内存地址总长度
 68 
 69 int led[LED_NUM] = {5,6,8,10};  /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
 70 
 71 static void __iomem *s3c_gpb_membase;        //定义一个指向一个IO内存空间的指针,后面做虚拟内存的映射
 72 
 73 
 74 #define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)           //将val的值写入reg地址中
 75 #define s3c_gpio_read(reg)       __raw_readl((reg)+s3c_gpb_membase)                //读取地址为reg中的值
 76 
 77 
 78 int dev_count = ARRAY_SIZE(led);            //设备结构体中设备的个数
 79 int dev_major = LED_MAJOR;                    //主设备号赋值给dev_major
 80 int dev_minor = 0;                            //次设备号为0
 81 int debug = DISABLE;                        //出错定义赋值
 82 
 83 static struct cdev      *led_cdev;            //定义一个cdev结构体类型的指针
 84 
 85 static int s3c_hw_init(void)                //硬件初始化函数
 86 {
 87     int          i;
 88     volatile unsigned long  gpb_con, gpb_dat, gpb_up;            //因为是保存寄存器地址,所以要用volatile防止被优化
 89 
 90     if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))        //申请一段IO内存空间,失败返回0
 91     {
 92         return -EBUSY;
 93     }
 94 
 95     if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )                //物理地址映射到虚拟地址,此时开启MMU
 96     {
 97         release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);                        //映射失败则释放申请的IO内存空间····
 98         return -ENOMEM;
 99     }
100 
101     for(i=0; i<dev_count; i++)
102     {
103         /* Set GPBCON register, set correspond GPIO port as input or output mode  */
104         gpb_con = s3c_gpio_read(GPBCON_OFFSET);
105         gpb_con &= ~(0x3<<(2*led[i]));   /* Clear the currespond LED GPIO configure register */
106         gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */
107         s3c_gpio_write(gpb_con, GPBCON_OFFSET);
108 
109         /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable  */
110         gpb_up = s3c_gpio_read(GPBUP_OFFSET);
111         //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */
112         gpb_up |= (0x1<<led[i]);  /* Disable pull up resister */
113         s3c_gpio_write(gpb_up, GPBUP_OFFSET);
114 
115         /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
116         gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
117         //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */
118         gpb_dat |= (0x1<<led[i]);  /* This port set to high level, then turn LED off */
119         s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
120     }
121 
122     return 0;
123 }
124 
125 
126 static void turn_led(int which, unsigned int cmd)            //调用ioct控制单个led灯的亮灭
127 {
128     volatile unsigned long  gpb_dat;
129 
130     gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
131 
132     if(LED_ON == cmd)
133     {
134         gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */
135     }
136     else if(LED_OFF == cmd)
137     {
138         gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */
139     }
140 
141     s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
142 }
143 
144 static void s3c_hw_term(void)                    //调用LED结束后释放内存所占用的资源及设置相应的GPIO引脚关闭LED
145 {
146     int                     i;
147     volatile unsigned long  gpb_dat;
148 
149     for(i=0; i<dev_count; i++)
150     {
151         gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
152         gpb_dat |= (0x1<<led[i]);  /* Turn LED off */
153         s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
154     }
155 
156     release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);            //释放指定的IO内存资源
157     iounmap(s3c_gpb_membase);                                //取消ioremap所做的映射
158 }
159 
160 
161 static int led_open(struct inode *inode, struct file *file)            //驱动功能函数
162 {
163     int minor = iminor(inode);                                //通过索引节点获得次设备号
164 
165     file->private_data = (void *)minor;                        //将次设备号保存到private_data中,方便在函数中调用
166     //private_data在系统调用期间保存各种状态信息
167     printk(KERN_DEBUG "/dev/led%d opened.\n", minor);        //打印成功信息
168     return 0;
169 }
170 
171 static int led_release(struct inode *inode, struct file *file)            //关闭led函数
172 {
173     printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
174 
175     return 0;
176 }
177 
178 static void print_help(void)
179 {
180     printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
181     //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
182     printk("Turn LED on command  : %u\n", LED_ON);
183     printk("Turn LED off command : %u\n", LED_OFF);
184 
185     return;
186 }
187 
188 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)        //led中断控制函数
189 {
190     int which = (int)file->private_data;                        //接受次设备号
191 
192     switch (cmd)                                                //判断参数要实现的功能
193     {
194         case LED_ON:
195 
196             turn_led(which, LED_ON);
197             break;
198 
199         case LED_OFF:
200             turn_led(which, LED_OFF);
201             break;
202     
203         default:                                                //如果传进来的既不是ON也不是OFF则打印出错并打印HELP结构体
204             printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
205             print_help();
206             break;
207     }
208 
209     return 0;
210 }
211 
212 
213 //结构体file_operationgs用来存储驱动内核模块提供的对设备进行各种操作的函数的指针
214 static struct file_operations led_fops = 
215 {
216     .owner = THIS_MODULE,        //指向拥有该结构体模块的指针,避免正在操作时被卸载。
217     .open = led_open,            //传递led_open函数打开设备
218     .release = led_release,        //传递led_release函数关闭设备
219     .unlocked_ioctl = led_ioctl,        //传递中断函数,不使用BLK的文件系统,将使用此种函数的指针代替ioctl
220 };
221 
222 static int __init s3c_led_init(void)        //底层函数
223 {
224     int                    result;
225     dev_t                  devno;
226 
227     if( 0 != s3c_hw_init() )                //初始化led硬件并判断是否初始化成功
228     {
229         printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
230         return -ENODEV;
231     }
232 
233     /*  Alloc the device for driver */
234     if (0 != dev_major) /*  Static */        
235     {
236         devno = MKDEV(dev_major, 0);            //通过主设备号与次设备号构建32位设备号
237         result = register_chrdev_region (devno, dev_count, DEV_NAME);            //已知主设备号,静态获得设备号并注册
238     }
239     else
240     {
241         result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);        //设备号未知,动态分配设备号并注册
242         dev_major = MAJOR(devno);            //根据设备号devno获得主设备号
243     }
244 
245     /*  Alloc for device major failure */
246     if (result < 0)
247     {
248         printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);        //设备号分配失败打印错误
249         return -ENODEV;
250     } 
251     printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);                //设备号分配成功打印主设备号
252 
253     if(NULL == (led_cdev=cdev_alloc()) )                //创建一个cdev结构体
254     {
255         printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
256         unregister_chrdev_region(devno, dev_count);
257         return -ENOMEM;
258     }
259 //cdev_alloc主要完成空间的申请和简单的初始化操作,分配一个cdev如果失败则打印错误信息并释放分配的设备号。
260     
261     led_cdev->owner = THIS_MODULE;                //指明设备所属模块,这里永远指向THIS_MODULE
262 //初始化cdev的file_operations,使字符设备与操作的函数绑定,led_fops结构体中的成员指向驱动提供的功能函数   
263     cdev_init(led_cdev, &led_fops);                
264 
265 //cdev_add为注册设备函数,通常发生在驱动模块的加载函数中并将返回值传递给result,为0则表示成功。
266     result = cdev_add(led_cdev, devno, dev_count);
267     if (0 != result)
268     {   
269         printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); 
270         goto ERROR;                //注册失败打印错误并跳转到出错处理
271     }
272 
273 
274     printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n",         
275             DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);                //注册成功则打印设备信息
276     return 0;
277 
278 
279 ERROR:
280     printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
281     cdev_del(led_cdev);                                    //注销设备,通常发生在驱动模块的卸载函数中
282     unregister_chrdev_region(devno, dev_count);            //释放注册的设备号
283     return result;
284 }
285 
286 static void __exit s3c_led_exit(void)                    //卸载驱动模块函数
287 {
288     dev_t devno = MKDEV(dev_major, dev_minor);
289 
290     s3c_hw_term();
291 
292     cdev_del(led_cdev);
293     unregister_chrdev_region(devno, dev_count);
294 
295     printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", 
296             DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);
297 
298     return ;
299 }
300 
301 
302 
303 /* These two functions defined in <linux/init.h> */
304 module_init(s3c_led_init);                            //insmod加载内核模块定义的宏
305 module_exit(s3c_led_exit);                            //rmmod卸载模块定义的宏,退出内核模块
306 
307 module_param(debug, int, S_IRUGO);
308 module_param(dev_major, int, S_IRUGO);
309 
310 MODULE_AUTHOR(DRV_AUTHOR);
311 MODULE_DESCRIPTION(DRV_DESC);
312 MODULE_LICENSE("GPL");

在此驱动中,第一步装载驱动调用module_init函数 -> 调用s3c_led_init函数 -> 调用s3c_hw_init函数初始化硬件;第二步s3c_led_init函数 -> 调用MKDEV或者alloc_chrdev_region函数分配主次设备号;第三步调用file_operations结构体定义led_fops结构体;第四步s3c_led_init函数 -> 调用cdev_alloc函数创建一个cdev的结构体 -> 调用cdev_init函数将字符设备与led_fops函数绑定;第五步调用cdev_add函数注册cdev字符设备。

驱动程序写好了那么fl2440开发板上已经有了led的驱动,这个时候需要一个应用程序来控制LED的亮灭:

 1 #include <stdio.h>
 2 #include <sys/ioctl.h>
 3 #include <sys/stat.h>
 4 #include <unistd.h>
 5 #include <stdarg.h>
 6 #include <errno.h>
 7 #include <sys/types.h>
 8 #include <fcntl.h>
 9 #include <string.h>
10 
11 #define LED_NUM         4
12 #define DEVNAME_LEN     10
13 
14 #define PLATDRV_MAGIC             0x60
15 #define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)
16 #define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)
17 
18 int main(int argc, char **argv)
19 {
20     int fd[LED_NUM];
21     int i, j;
22     char devname[DEVNAME_LEN] = {0};
23     
24     for(i = 0; i < LED_NUM; i++)
25     {
26         snprintf(devname, sizeof(devname), "/dev/led%i",i);
27         fd[i] = open(devname, O_RDWR,0755);
28         if(fd[i] < 0)
29         {
30             printf("Can not open %s: %s", devname, strerror(errno));
31             for(j = 0; j < i; j++)
32             {
33                 if(fd[j] > 0)
34                     close(fd[j]);
35             }
36             return -1;
37         }
38     }    
39     
40     while(1)
41     {
42         for(i = 0; i < LED_NUM; i++)
43         {
44             ioctl(fd[i], LED_ON);
45             sleep(1);
46             ioctl(fd[i], LED_OFF);
47         }
48     }
49     
50     for(i = 0; i < LED_NUM; i++)
51     {
52         close(fd[i]);
53     }
54     return 0;
55 }

因为这个应用程序是要在fl2440开发板上面跑的,所以Linux自带的gcc编译器编译出来的可执行文件是不行的,这个时候我们需要用交叉编译器来完成这个操作了:

[xiaohexiansheng@centos6 led_s3c2440]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc ledapp.c -o ledapp

[xiaohexiansheng@centos6 led_s3c2440]$ ls
ledapp ledapp.c Makefile s3c_led.c s3c_led.ko

将ledapp与s3c_led.ko用tftp拷贝到开发板上面,下面是在开发板上面的操作:

>: tftp -gr s3c_led.ko 192.168.1.38
s3c_led.ko 100% |*******************************| 6214 0:00:00 ETA
>: tftp -gr ledapp 192.168.1.38
ledapp 100% |*******************************| 6005 0:00:00 ETA
>: chmod 777 ledapp

>: insmod s3c_led.ko
S3C led driver[major=252] version 1.0.0 installed successfully!    (这里我们可以获取到注册的主设备号是【252】)
>: Dec 31 17:01:19 root kern.debug kernel: S3C led driver use major 252
Dec 31 17:01:19 root kern.err kernel: S3C led driver[major=252] version 1.0.0 installed successfully

因为在驱动程序中没有自动的创建次设备节点,所以在这里需要手动创建次设备节点。

>: mknod /dev/led0 c 252 0
>: mknod /dev/led1 c 252 1
>: mknod /dev/led2 c 252 2

>: mknod /dev/led3 c 252 3

>: ./ledapp

posted @ 2016-05-10 13:29  小禾先生  阅读(262)  评论(0编辑  收藏  举报