linux驱动初探之字符驱动

关键字:字符驱动、动态生成设备节点、helloworld

linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~

  此驱动程序基于linux3.0的内核,exynos4412开发板。

首先,打开平台文件,此开发板的平台文件是arch\arm\mach-exynos\mach-itop4412.c,不同平台位置是不一样的。

  申明一下设备信息,这里以编译进kernel为例

1 #ifdef CONFIG_HELLO_CHAR_CTL
2 struct platform_device s3c_device_hello_char_ctl = {
3         .name   = "Hello_Jni_Char",
4         .id             = -1,
5 };
6 #endif

然后在struct platform_device *smdk4x12_devices[]数组里注册设备信息

1 #ifdef CONFIG_HELLO_CHAR_CTL
2     &s3c_device_hello_char_ctl,
3 #endif

至此,平台文件配置完毕。

接下来,开始大餐,驱动程序的编写!

注意:在linux的每一个驱动程序里面都需要加上如下信息,表明开源

1 MODULE_LICENSE("Dual BSD/GPL");
2 MODULE_AUTHOR("pngcui");

每一个驱动程序都是从init函数开始的,同时也需要编写exit函数,在初始化函数中需要生成主设备号与从设备号,因为linux是根据主从设备号来寻找相应的硬件的

 1 #define DEV_MAJOR 0
 2 #define DEV_MINOR 0
 3
 4 int numdev_major = DEV_MAJOR;
 5 int numdev_minor = DEV_MINOR;
 6
 7
 8 /*输入主设备号*/
 9 module_param(numdev_major,int,S_IRUSR);
10 /*输入次设备号*/
11 module_param(numdev_minor,int,S_IRUSR);

进入驱动程序中的init函数中,进行注册设备号,生成设备节点DEVICE_NAME

1 #define DEVICE_NAME "chardevnode"

注意:此驱动由于控制了两个gpio引脚所以出现了gpio_init(),在这里可以忽略,对于gpio的控制,在下一篇博客中会写到。

 1 static int scdev_init(void)
 2 {
 3     int ret = 0,i;
 4     dev_t num_dev;
 5
 6
 7     printk(KERN_EMERG "numdev_major is %d!\n",numdev_major);
 8     printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor);
 9
10     if(numdev_major){
11         num_dev = MKDEV(numdev_major,numdev_minor);
12         ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
13     }
14     else{
15         /*动态注册设备号*/
16         ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
17         /*获得主设备号*/
18         numdev_major = MAJOR(num_dev);
19         printk(KERN_EMERG "adev_region req %d !\n",numdev_major);
20     }
21     if(ret<0){
22         printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
23     }
24     myclass = class_create(THIS_MODULE,DEVICE_NAME);
25
26
27     my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
28     if(!my_devices){
29         ret = -ENOMEM;
30         goto fail;
31     }
32     memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
33
34     /*设备初始化*/
35     for(i=0;i<DEVICE_MINOR_NUM;i++){
36         my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
37         memset(my_devices[i].data,0,REGDEV_SIZE);
38         /*设备注册到系统*/
39         reg_init_cdev(&my_devices[i],i);
40
41         /*创建设备节点*/
42         device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i);
43     }
44
45     ret = gpio_init();
46     if(ret){
47         printk(KERN_EMERG "gpio_init failed!\n");
48     }
49
50     printk(KERN_EMERG "scdev_init!\n");
51     return 0;
52
53 fail:
54     /*注销设备号*/
55     unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
56     printk(KERN_EMERG "kmalloc is fail!\n");
57
58     return ret;
59 }
60
61 static void scdev_exit(void)
62 {
63     int i;
64     printk(KERN_EMERG "scdev_exit!\n");
65
66     /*除去字符设备*/
67     for(i=0;i<DEVICE_MINOR_NUM;i++){
68         cdev_del(&(my_devices[i].cdev));
69         /*摧毁设备节点函数*/
70         device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i));
71     }
72     /*释放设备class*/
73     class_destroy(myclass);
74     /*释放内存*/
75     kfree(my_devices);
76
77     /*释放GPIO*/
78     for(i=0;i<LED_NUM;i++){
79         gpio_free(led_gpios[i]);
80     }
81
82     unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
83 }
84
85
86 module_init(scdev_init);
87 /*初始化函数*/
88 module_exit(scdev_exit);
89 /*卸载函数*/

然后把设备注册到系统

 1 static void reg_init_cdev(struct reg_dev *dev,int index){
 2     int err;
 3     int devno = MKDEV(numdev_major,numdev_minor+index);
 4
 5     /*数据初始化*/
 6     cdev_init(&dev->cdev,&my_fops);
 7     dev->cdev.owner = THIS_MODULE;
 8     dev->cdev.ops = &my_fops;
 9
10     /*注册到系统*/
11     err = cdev_add(&dev->cdev,devno,1);
12     if(err){
13         printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
14     }
15     else{
16         printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);
17     }
18 }
dev->cdev.ops = &my_fops;这一句就指明了此驱动程序的函数接口
1 struct file_operations my_fops = {
2     .owner = THIS_MODULE,
3     .open = chardevnode_open,
4     .release = chardevnode_release,
5     .unlocked_ioctl = chardevnode_ioctl,
6     .read = chardevnode_read,
7     .write = chardevnode_write,
8     .llseek = chardevnode_llseek,
9 };

在这里的所有函数接口必须都实现,否则会编译报错。

其中最重要的两个函数是open、ioctl这两个函数,open函数用来打开设备节点,然后才可以对设备进行驱动。

然后是ioctl函数,一般上层应用都是通过调用此函数来对设备进行操作的。

 1 /*打开操作*/
 2 static int chardevnode_open(struct inode *inode, struct file *file){
 3     printk(KERN_EMERG "chardevnode_open is success!\n");
 4
 5     return 0;
 6 }
 7 /*关闭操作*/
 8 static int chardevnode_release(struct inode *inode, struct file *file){
 9     printk(KERN_EMERG "chardevnode_release is success!\n");
10
11     return 0;
12 }
13 /*IO操作*/
14 static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
15
16     switch(cmd)
17     {
18         case 0:
19         case 1:
20             if (arg > LED_NUM) {
21                 return -EINVAL;
22             }
23
24             gpio_set_value(led_gpios[arg], cmd);
25             break;
26
27         default:
28             return -EINVAL;
29     }
30
31     printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
32
33     return 0;
34 }
35
36 ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
37     return 0;
38 }
39
40 ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
41     return 0;
42 }
43
44 loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
45     return 0;
46 }

到这里一个完整的字符驱动就编写完毕了。

最后附上完整的驱动程序

  1 /*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/
  2 #include <linux/init.h>
  3 /*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/
  4 #include <linux/module.h>
  5 /*定义module_param module_param_array的头文件*/
  6 #include <linux/moduleparam.h>
  7 /*定义module_param module_param_array中perm的头文件*/
  8 #include <linux/stat.h>
  9 /*三个字符设备函数*/
 10 #include <linux/fs.h>
 11 /*MKDEV转换设备号数据类型的宏定义*/
 12 #include <linux/kdev_t.h>
 13 /*定义字符设备的结构体*/
 14 #include <linux/cdev.h>
 15 /*分配内存空间函数头文件*/
 16 #include <linux/slab.h>
 17 /*包含函数device_create 结构体class等头文件*/
 18 #include <linux/device.h>
 19
 20 /*自定义头文件*/
 21 #include "char_driver_leds.h"
 22
 23 /*Linux中申请GPIO的头文件*/
 24 #include <linux/gpio.h>
 25 /*三星平台的GPIO配置函数头文件*/
 26 /*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/
 27 #include <plat/gpio-cfg.h>
 28 /*三星平台4412平台,GPIO宏定义头文件*/
 29 #include <mach/gpio-exynos4.h>
 30
 31
 32 MODULE_LICENSE("Dual BSD/GPL");
 33 /*声明是开源的,没有内核版本限制*/
 34 MODULE_AUTHOR("iTOPEET_dz");
 35 /*声明作者*/
 36
 37 static int led_gpios[] = {
 38     EXYNOS4_GPL2(0),EXYNOS4_GPK1(1),
 39 };
 40 #define LED_NUM        ARRAY_SIZE(led_gpios)
 41
 42 #define DEVICE_NAME "chardevnode"
 43
 44 #define DEVICE_MINOR_NUM 1
 45
 46 #define DEV_MAJOR 0
 47 #define DEV_MINOR 0
 48 #define REGDEV_SIZE 3000
 49
 50
 51 struct reg_dev
 52 {
 53     char *data;
 54     unsigned long size;
 55
 56     struct cdev cdev;
 57 };
 58
 59
 60 int numdev_major = DEV_MAJOR;
 61 int numdev_minor = DEV_MINOR;
 62
 63 /*输入主设备号*/
 64 module_param(numdev_major,int,S_IRUSR);
 65 /*输入次设备号*/
 66 module_param(numdev_minor,int,S_IRUSR);
 67
 68 static struct class *myclass;
 69 struct reg_dev *my_devices;
 70
 71 /*打开操作*/
 72 static int chardevnode_open(struct inode *inode, struct file *file){
 73     printk(KERN_EMERG "chardevnode_open is success!\n");
 74
 75     return 0;
 76 }
 77 /*关闭操作*/
 78 static int chardevnode_release(struct inode *inode, struct file *file){
 79     printk(KERN_EMERG "chardevnode_release is success!\n");
 80
 81     return 0;
 82 }
 83 /*IO操作*/
 84 static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
 85
 86     switch(cmd)
 87     {
 88         case 0:
 89         case 1:
 90             if (arg > LED_NUM) {
 91                 return -EINVAL;
 92             }
 93
 94             gpio_set_value(led_gpios[arg], cmd);
 95             break;
 96
 97         default:
 98             return -EINVAL;
 99     }
100
101     printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
102
103     return 0;
104 }
105
106 ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
107     return 0;
108 }
109
110 ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
111     return 0;
112 }
113
114 loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
115     return 0;
116 }
117 struct file_operations my_fops = {
118     .owner = THIS_MODULE,
119     .open = chardevnode_open,
120     .release = chardevnode_release,
121     .unlocked_ioctl = chardevnode_ioctl,
122     .read = chardevnode_read,
123     .write = chardevnode_write,
124     .llseek = chardevnode_llseek,
125 };
126
127
128 /*设备注册到系统*/
129 static void reg_init_cdev(struct reg_dev *dev,int index){
130     int err;
131     int devno = MKDEV(numdev_major,numdev_minor+index);
132
133     /*数据初始化*/
134     cdev_init(&dev->cdev,&my_fops);
135     dev->cdev.owner = THIS_MODULE;
136     dev->cdev.ops = &my_fops;
137
138     /*注册到系统*/
139     err = cdev_add(&dev->cdev,devno,1);
140     if(err){
141         printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
142     }
143     else{
144         printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);
145     }
146 }
147
148 static int gpio_init(void){
149     int i=0,ret;
150
151     for(i=0;i<LED_NUM;i++){
152         ret = gpio_request(led_gpios[i], "LED");
153         if (ret) {
154             printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,i,ret);
155             return -1;
156         }
157         else{
158             s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
159             gpio_set_value(led_gpios[i], 1);
160         }
161     }
162
163     return 0;
164 }
165
166
167 static int scdev_init(void)
168 {
169     int ret = 0,i;
170     dev_t num_dev;
171
172
173     printk(KERN_EMERG "numdev_major is %d!\n",numdev_major);
174     printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor);
175
176     if(numdev_major){
177         num_dev = MKDEV(numdev_major,numdev_minor);
178         ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
179     }
180     else{
181         /*动态注册设备号*/
182         ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
183         /*获得主设备号*/
184         numdev_major = MAJOR(num_dev);
185         printk(KERN_EMERG "adev_region req %d !\n",numdev_major);
186     }
187     if(ret<0){
188         printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
189     }
190     myclass = class_create(THIS_MODULE,DEVICE_NAME);
191
192
193     my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
194     if(!my_devices){
195         ret = -ENOMEM;
196         goto fail;
197     }
198     memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
199
200     /*设备初始化*/
201     for(i=0;i<DEVICE_MINOR_NUM;i++){
202         my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
203         memset(my_devices[i].data,0,REGDEV_SIZE);
204         /*设备注册到系统*/
205         reg_init_cdev(&my_devices[i],i);
206
207         /*创建设备节点*/
208         device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i);
209     }
210
211     return 0;
212
213 fail:
214     /*注销设备号*/
215     unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
216     printk(KERN_EMERG "kmalloc is fail!\n");
217
218     return ret;
219 }
220
221 static void scdev_exit(void)
222 {
223     int i;
224     printk(KERN_EMERG "scdev_exit!\n");
225
226     /*除去字符设备*/
227     for(i=0;i<DEVICE_MINOR_NUM;i++){
228         cdev_del(&(my_devices[i].cdev));
229         /*摧毁设备节点函数d*/
230         device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i));
231     }
232     /*释放设备class*/
233     class_destroy(myclass);
234     /*释放内存*/
235     kfree(my_devices);
236
237     /*释放GPIO*/
238     for(i=0;i<LED_NUM;i++){
239         gpio_free(led_gpios[i]);
240     }
241
242     unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
243 }
244
245
246 module_init(scdev_init);
247 /*初始化函数*/
248 module_exit(scdev_exit);
249 /*卸载函数*/

以及完整的测试程序

 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <unistd.h>
 5 #include <sys/ioctl.h>
 6 #include <stdio.h>
 7
 8 int main(){
 9
10     int fd;
11
12     if((fd = open("/dev/chardevnode0",O_RDWR|O_NDELAY)) < 0)
13         printf("Hello_Jni open failed!\n");
14
15     ioctl(fd,0,0);
16     ioctl(fd,0,1);
17
18     close(fd);
19
20     return 0;
21 }
时间: 2024-10-02 10:30:05

linux驱动初探之字符驱动的相关文章

如何写分层驱动(复杂的字符驱动)----以lcd驱动为例

*********如何写分层驱动(复杂的字符驱动)----以lcd驱动为例************** 思路:复杂的驱动都是建立在简单的驱动的基础上,所以首先要知道内核简单字符设备驱动如何写 1.如何简单驱动程序 1.1 构造file_operations .open = drv_open .read = drv_read 1.2 告诉内核有1.1这个结构,register_chrdev(主设备号,fop,name) 上面可以被下面三句话代替 分配cdev 设置cdev cdev_add 1.3

MPU6050带字符驱动的i2c从设备驱动1

开干: 1.闲言碎语 这个驱动,越写觉的越简单,入门难,入门之后感觉还好.Linux开发还是比较友好的. 2.编写MPU6050带字符驱动的i2c从设备驱动 要实现的功能就是,将MPU6050作为字符驱动,在应用层,对其进行读数据.实现简单的功能.在前面的分析和实践中,可以看到实现字符驱动主要是实现file_operation中的方法,注册初始化cdev,让cdev和file_opration产生联系,字符驱动的初始化通过module_init来声明.实现i2c从设备驱动,主要是i2c_clie

Linux字符驱动

学习完了字符驱动,是按照宋宝华的Linux设备驱动开发讲解学习的,代码练习敲了一遍,自己也理解了. 字符驱动主要的就是一些open,close,read,write等操作 通过上层调用到自己写的底层函数 这里写代码片 #include <linux/fs.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/errno.

Linux设备驱动第七篇:高级字符驱动操作之阻塞IO

我们之前介绍过简单的read,write操作,那么会有一个问题:当驱动无法立即响应请求该怎么办?比如一个进程调用read读取数据,当没有数据可读时该怎么办,是立即返回还是等到有数据的时候:另一种情况是进程调用write向设备写数据,如果缓冲区满了或者设备正忙的时候怎么办,是立即返回还是继续等待直到设备可写?这种情况下,一般的缺省做法是使进程睡眠直到请求可以满足为止.本篇就介绍遇到这类问题驱动的处理方法. 睡眠 什么是睡眠?一个进程睡眠意味着它暂时放弃了CPU的运行权,直到某个条件发生后才可再次被

从Linux内核LED驱动来理解字符设备驱动开发流程

目录 博客说明 开发环境 1. Linux字符设备驱动的组成 1.1 字符设备驱动模块加载与卸载函数 1.2 字符设备驱动的file_operations 结构体中的成员函数 2. 字符设备驱动--设备号注册卸载 2.1 设备号注册 2.2 设备号注销 3. 字符设备驱动--文件操作 参考资料 示例代码 @(从Linux内核LED驱动来理解字符设备驱动开发流程) 博客说明 撰写日期 2018.12.08 完稿日期 2019.10.06 最近维护 暂无 本文作者 multimicro 联系方式 [

linux驱动学习(1)——字符设备驱动开发

(一)驱动程序介绍 (a)Linux驱动程序学习 知识结构: 1. Linux驱动程序设计模式(40%) 2. 内核相关知识(30%) 3. 硬件相关知识(30%) (b)驱动分类: ①字符设备: 字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用. ②块设备: 在大部分的 Unix 系统, 块设备不能按字节处理数据,只能一次传送一个或多个长度是512字节( 或一个更大的 2 次幂的数 )的整块数据,而Lin

Linux驱动框架之framebuffer驱动框架

1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲(framebuffer) 是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作.这种操作是抽象的,统一的.用 户不必关心物理显存的位置.换页机制等等具体细节.这些都是由Framebuffer设备驱动来完成的. (2)从驱动来看,f

14、字符驱动编程模型

字符驱动编程模型 上面就是内核的cdev的结构.他只有6个成员.有一些是内核自己回去用的,有一些是我们程序员要去用的.例如:count,表明设备有几个可以用.例如,我的开发板支持三个串口.所以count=3.还有设备号:查看设备文件: 我们从前面知道,我们是通过字符设备文件来访问我们的字符设备驱动的.两者是通过主设备号来建立联系的. 一个主设备好可以对应多个此设备号.他们是同一类型的设备.驱动程序就是通过次设备号来区分是串口1还是串口2的 设备号的操作 设备号分配 设备号-注销 这是一个很重要的

字符设备驱动、平台设备驱动、设备驱动模型、sysfs的关系

Linux驱动开发的童鞋们来膜拜吧:-)  学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术.对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了.要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码.网络上有关这些技术的文章不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关联的分析.对于开发者而言,能够熟悉某一点并分享出来已很难得,但对于专注传授技术和经验给