Linux驱动入门总结
Linux驱动入门总结
1. linux用户空间和内核空间.
用户空间:包含用户编写的应用软件,c库整个4g的虚拟地址空间,用户空间包含了0-3g。
1用户不能访问内核的地址空间,包括代码和数据,不能直接操作硬件
2.用户程序访问硬件通过系统调用和硬件中断来实现访问和两者数据转移。
3.用户和内核都有自己独立的栈。
内核空间:包含驱动,文件系统,平台代码等等,内核空间包含了剩余1g(3g-4g),内核1g空间对于所有的进程都是共享。
1.用户在用户空间运行的时间有自己的栈,如果进行了系统调用进入的内核的访问空间,内核会为每个用户的进程分配8k的栈空间。
2.用户空间的栈非常大 内核空间中给每个进程的栈只有8k 局部变量的定义大小不能大于8k
注意:用户空间和内核空间的划分本质要依赖arm的工作模式:用户空间的软件在运行时,cpu处于usr模式,内核空间的软件运行时,cpu处于svc模式,这种模式的划分,决定了用户空间软件和内核空间的软件在访问资源的时,具有的权限是不一样的!例如,如果在应用程序访问null指针,操作系统有权干掉这个应用程序。如果在内核编程时,访问null指针,造成系统崩溃!
2. Linux系统调用的过程.(举例ioctl的实现)
应用程序通过系统调用函数如何调用到驱动的struct file_opreations相关的函数呢?
1.struct inode(应用层) :linux内核用此结构来描述一个文件的物理信息。
从驱动的角度只关心应用层两个字段:
i_rdev 保存设备号, mknod创建设备文件时保存的设备号.
i_cdev 保存字符设备对象指针,mknod时,内核分配初始化inode对象,然后根据设备号从内核cdev数组中取出之前注册的字符设备对象led_cdev指针,赋值给i_cdev
inode申明周期:内核从创建设备文件开始,销毁设备文件结束!
2.struct file(应用层):linux内核用此结构体来描述一个文件被打开以后的状态信息,从驱动角度只需关注:f_op 最终指向驱动的struct file_opeations硬件操作集合
file结构体的生命周期:从open时内核创建此结构体对象来描述文件打开后的状态,close关闭文件时,内核销毁对象。
struct cdev {// cdev的注册,linux内核里维护了一个cdev_map的表(哈希表) (内核级)
….
conststruct file_operations *ops; // 相关操作接口
dev_tdev; //设备号};
注: 一个文件只有唯一的inode对象,可以有多个file对象!
3.struct file_operations 相关操作接口(内核级):
int (*open)(struct inode *inode,struct file *file);int (*realse)(struct inode *inode,struct file *file);
以上两个函数,底层驱动可以不用实现,如果不做实现,用户空间的open永远返回成功
对设备读写操作:
ssize_t (*read)(struct file*file,char __user *buf,size_t count,loff_t *ppos);
ssize_t (*write(struct file*file,char __user *buf,size_t count,loff_t *ppos);
4.应用程序调用设备程序相关接口struct file_operations 过程:
4.1驱动安装:
1.驱动程序分配初始化硬件操作对象led_fops,并且给出相关的操作方法(led_open,led_close,led_read)
2.驱动分配初始化注册字符设备对象led_cdev,并且将硬件操作集合led_fops赋值led_cdev .ops。这样完成了给字符设备驱动提供。操作硬件的方法
3.注册字符设备对象,其实就是以设备号为索引,将字符设备对象的指针&led_cdev添加到内核的cdev数组中。
4. 创建设备文件:应用程序利用mknod创建设备文件,内核会给这个设备文件分配一个struct inode对象,然后把设备号赋值给i_rdev。
5.内核然后以设备号为索引,在内核的cdev数组中找到之前注册的字符设备对象指针&led_cdev,然后将这个对象指针赋值给inode的i_cdev指针。
设备文件打开结论:
open打开设备文件结论:file->f_op=&led_fops;
应用程序调用read:1.APP:read ->软中断 ->sys_read:2.sys_read 函数就执行一句话:
通过fd获取关联的struct file
file ->f_op ->read(...)=&led_fops ->read =led_read
应用程序调用close:
1.close ->软中断 ->sys_close 2:sys_close: 通过fd获取关联的struct file
file -> f_op ->release =&led_fops ->release =led_close
运行应用程序时:
对文件的访问永远先open:APP:open打开设备文件时:
1.先调用c库的open,c库的open将open的系统调用号保存在r7中,调用swi或者svc触发软件中断(c库)
2.cpu跳转到内核的异常向量表的入口vector_swi位置
3.根据r7的系统调用号,在系统调用表中找到open的内核实现函数sys_open
4.sys_open函数中要做如下事情:
5.sys_open会分配struct file对象,从已知的字符设备对象led_cdev中的ops取出的硬件操作集合led_fops
6.将led_fops的指针赋值给file对象的f_op
7.然后将设备文件描述符fd和struct file进行绑定关联
8.然后判断一下底层驱动led_fops中有没有open函数的实现(led_open)如果有,调用它,如果没有,给用户永远返回成功!
注意:底层驱动的各个函数返回值是给内核对应的sys_xxx函数使用,不是给应用的系统调用函数
总结:open相当重要! 完成了struct file_operations 和struct file结构体的关联!
3. Linux内核配置编译过程.
配置命令:make config:遍历选择所要编译的内核特性
make allyesconfig:配置所有可编译的内核特性
make allnoconfig:并不是所有的都不编译,而是能选的都回答为NO、只有必须的都选择为yes。
make menuconfig:这种就是打开一个文件窗口选择菜单,这个命令需要打开的窗口大于80字符的宽度,打开后就可以在里面选择要编译的项了
make mrproper -----删除不必要的文件和目录.
make config(基于文本的最为传统的配置界面,不推荐使用)。
make xconfig(基于图形窗口模式的配置界面,Xwindow下推荐使用)。
make oldconfig(如果只想在原来内核配置的基础上修改一些小地方,会省去不少麻烦。
Makefile:分布在 Linux 内核源代码中的Makefile,定义 Linux 内核的编译规则。
配置脚本:Kconfig文件,定义了各个模块和其对应的可配置选项
配置工具:配置命令解释器(如Make config、Make menuconfig 和 make xconfig),对配置脚本进行解释,提供基于字符或图形的用户配置选择界面。
配置文件:顶层.config文件,用户配置选择的初值和选择结果。
4. Linux驱动模块的结构.
下面是一个内核模块需要的结构:
1、模块加载函数(必须)
2、模块卸载函数(必须)
3、模块许可声明(必须,如果不声明LICENSE,模块被加载时会收到内核被污染警告——kernel tainted,这是内核发出的)
4、模块参数(可选)
5、模块导出符号(可选)
6、模块作者等信息声明(可选)
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL"); 告诉内核此模块遵循GPL协议,如果没有这样的声明,内核在装载模块时会产生内核污染的警告
注意:以后写任何一个.c内核代码,都必须写GPL协议
static int hello_init(void) //在执行insmod时会被调用
{
printk(KERN_ALERT "Hello World init!\n");
}
static int hello_exit(void) //在执行rmmod时会被调用
{
printk(KERN_ALERT "Hello World exit!\n");
}
module_init(hello_init); //这两行代码为实现在设备加载和卸载时自动执行指定函数入口地址
module_exit(hello_exit);
MODULE_AUTHOR("HC"); //指定开发的作者
MODULE_DESCRIPTION("A simple Hello World Module"); //对内核模块的相关描述
MODULE_ALIAS("a simplest module"); //最后要留一个空行,否则编译时会出现警告,这个原因在《C专家编程》中有讲述,跟链接时的问题有关系。
驱动一定是内核模块,内核模块不一定是驱动。内核模块(全称为动态可加载内核模块,简称模块),由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其它内核上层的功能。
5. linux内核模块相关命令.
insmod、rmmod、lsmod:加载、卸载、查看内核有的模块。Modinfo:查看模块依赖驱动。depmod是一个 用来产生modules.dep和map文件的程序。在modules.dep文件中空白行和以'#'开头的行将被忽略.depmod通过读取/lib /modules/version目录下的每一个模块来创建一个记录模块相依性的列表。这个列表就是/lib/modules/version目录下的 modules.dep。modprobe 命令是根据depmod -a的输出/lib/modules/version/modules.dep来加载全部的所需要模块。
6. 驱动程序一定要与具体的硬件设备关联吗?
驱动程序,普通的定义:是为了驱使硬件工作。驱动程序一般指的是设备驱动程序(DeviceDriver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,但它也只是程序的一种,你可以设计的与硬件无关,只是使用驱动的框架。如 LS 所述的,一些仿硬件的软件,都是使用了驱动的框架、但并没有真正驱使硬件。遥控坏了,有两个选择1.购买同品牌遥控,2.直接用万能遥控。道理一样,驱动可以使用原厂驱动,也可以用万能驱动.USB接口的摄像头,采用vl42的接口。
7. linux设备文件相关知识。
7.1linux内核设备驱动分类
1.字符设备:采用字节流形式访问. 按键、led、beep、uart、摄像头、声卡、GPS。
2.块设备:采用数据块进行访问,比如一次访问512字节,但是在linux系统中,也可以采用字节流的形式。主要是字符设备和块设备驱动程序提供的访问接口(file I/O API数据结构)是不一样的。硬盘、nandfalsh。
3. 网络设备:有线或者无线,一般要和tcp/ip/协议一起使用 tcp/ip协议属于物理层和数据链路层之间 其实属于数据链路层 wifi.
其实linux,unix都遵循“一切皆文件”的思想,所以对于设备来说,在用户空间的表现形式也是文件的形式存在,这是这个文件不是普通的文本文件,而是设备文件。设备文件在linux系统根文件系统中的/dev 目录下。设备文件是系统启动时动态创建在内存中,掉电丢失
7.2linux设备文件
1.设备文件:包含字符设备文件和块设备文件。 网络没有文件,必须通过套接字socket,识别2.文件本质上就是硬件设备!
2.设备文件组成,主设备号、次设备号、设备文件名,权限。
主设备号:应用程序根据设别文件的主设备号信息来找到内核里跟自己对应的驱动程序(.c)。一个驱动程序只有唯一的主设备号。
次设备号:如果驱动程序同时管理多个设备,可以通过次设备号来区分具体操作的那个硬件设备。
说明:一般使用次设备号来区分,应用在这些硬件设备的硬件特性相同
3.创建设备文件/设备号。:
创建字符设备文件:mknod /dev/设备文件名 c 主设备号 次设备号
创建块设备文件: mknod /dev/设备文件名 b 主设备号 次设备号
例如:
mknod /dev/myled c 250 0mknod /dev/mybeep c 249 0 mknod /dev/myuart c 248 0
4.设备号操作相关内容:
4.1设备号数据类型 数据类型为 dev_t(unsigned int)
4.2设备号:包含主设备号,次设备号 高12位存放主设备号 低12存放次设备号
主设备号= MAJOR(设备号); 次设备号= MINOR(设备号)设备号= MADEV(主设备号,次设备号)
4.3设备号申请的方法:静态申请和动态申请。
静态申请:1.系统启动完毕,首先执行cat /proc/devices 查看当前系统中哪个主设备处于空闲状态: 2.根据实际驱动要操作的硬件个数来决定次设备号3.可以提前在加载驱动之前创建设备文件4.内核驱动编写的函数中合并设备号5. 1.2两个步骤仅仅人为分配主设备号和次设备号,还需要正式向内核申请这个号。
register_chrdev_region(dev_t Dev,unsigned int count,char *name)(内核中实现);
功能:静态向内核申请设备号
dev: 合并号的设备号 count:设备的个数,起始说的就是次设备号的个数
name:设备名称,如果申请成功 cat /proc/devices 能看到
优点:能够自己选择申请设备号,保存.
缺点:不适合在别的开发板上面兼容性,因为不同的硬件不确定那些设备号已经使用.
动态申请:1.直接调用alloc_chrdev_region函数向内核申请,内核帮你分配设备号。
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
dev;内核帮你申请的设备号,保存分配到设备号 baseminor:希望分配的起始设备号,一般写0
count:需要分配的次设备号数目 name:设备名称(出现在/proc/devices)
优点:便于驱动在别的开发板上进行推广
缺点:在申请设备号之前,不能提前创建设备文件
注意:1.不管动态申请还是静态申请,主设备号不能为0.
2.设备号如果不在使用,一定要归还操作系统:
注销分配的设备号:
unregister_chrdev_region(dev_t dev,unsigned int coutn)dev:分配的设备号 count:次设备号的个数
5.设备文件的创建 --内核自动创建和自己手动创建.
设备文件在linux系统中用来描述硬件设备,看到设备文件就等于看到硬件设备,对硬件设备的操作,只需利用linux系统提供的系统调用函数对设备文件操作即可买也可以等价于对硬件设备的操作。
方法:
5.1.手动创建
mknod /dev/myled(设备文件名) c 250 0
注意:设备文件名仅仅就是给open函数使用,设备文件关键在于它包含的设备号信息,应用程序根据设备号找到驱动,而不是根据设备文件名找到驱动!
5.2 自动创建
实施步骤:
1.自动创建设备文件必须由mdev可执行程序来实现创建,通过配置busybox来支持mdev,新版本的busybox默认都是支持mdev。x86平台自动创建设备文件的可执行程序叫udev。当然程序可以下载udev源码,进行交叉编译,在嵌入式平台也能够利用udev来自动创建设备文件。mdev或者udev才是真正自动创建设备文件的人! which is mdev //查看开发板是否支持mdev
2.根文件系统rootfs必须在它的配置文件etc/fstab中添加两个重要
的文件系统支持:
proc /proc proc default 0 0
sysfs /sys sys default 0 0
说明:procfs和sysfs都是虚拟文件系统,那么对应的挂节点/proc和/sys目录下的内容都是在内存中!
3.根文件系统roofs必须在它的启动脚本中etc/init.d/rcS添加如下语句:
mount -a //就是用来根据fstab完成一系列挂接操作
echo /sbin/mdev > /proc/sys/kernnel/hotplug //将mdev所在的路径
和mdev重定向到hotplug文件,以后驱动准备好创建设备文件的原材料(主设备号,次设备号,设备文件名)以后,在根据hotplug文件,调用mdev完成设备文件的自动创建
mdev -s //系统在启动时,创建内核已有的驱动程序对应的设备文件。
#include <linux/uaccess.h>
4.驱动程序只需4个函数即可完成设备文件的创建和销毁
struct class *cls; //创建设备类指针(树枝)苹果设备文件
cls = class_creat (THIS_MODULE, "myleds"); //创建设备类,设备类名为myleds,根设备名和设备文件名没有关系,等于创建了一个树枝!创建设备类的结果是在/sys/class 目录下创建跟设备类同名的目录,这个目录下存放创建设备文件使用的原材料!
/sys/class/myleds
device_creat(cls,NULL,dev,NULL,"myled");dev: 就是申请好的设备号,原材料;
myled:就是将来创建的设备文件名,原材料;
说明:表面上看,device_create是帮你自动创建设备文件,其实这个函数仅仅是为创建设备文件准备原材料而已,真正创建设备文件是内核调用mdev ,./dev/myled
自动销毁设备文件:
删除设备文件: device_destroy(cls,dev);cls:设备类指针 dev:设备号
删除设备类 class_destroy(cls,);
6.linux内核描述字符设备驱动涉及的数据结构:
6.1内核使用struct cdev结构体来描述字符设别
struct cdev{#include <linux.cdev.h> cdev
const struct file_openration *ops;//设备文件的操作指针(方法)
//给字符设备提供相关操作硬件的方法(函数),ops仅仅是一个指针,在实例化一个
字符设别对象时,一定要将其指向一个对象。
dev_t dev;//保存记录成功申请的设备号
unsigned int count;//记录字符设备驱动管理的硬件设备个数或者次设备个数
};
typedef void(*fuction)(void); //函数指针(本质上为地址)
functon reset;
6.2给字符设备提供操作硬件的方法涉及的结构体:
struct file_operations{#include <linux/fs.h> file_operations
int (*open)(..)=led_config;
int (*release)(..);
int (*read)(.....);
int (*write)(.....);
Int (*ioctl)(.....); //重要接口
........
}
8. linux设备号与设备文件的区别。
当我们访问一个设备节点是,系统是如果知道使用哪个设备驱动及访问哪个设备的呢?这个是通过设备号来实现的。当我们创建一个设备节点时需要指定主设备号和次设备号。对于设备节点来说,名字不是重要的,设备号才是最重要的,它实际指定了对应的驱动程序和对应的设备。
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。
Linux为所有的设备文件都提供了统一的操作函数接口,方法是使用数据结构structfile_operations。这个数据结构中包括许多操作函数的指针,如open()、close()、read()和write()等,但由于外设的种类较多,操作方式各不相同。Structfile_operations结构体中的成员为一系列的接口函数,如用于读/写的read/write函数和用于控制的ioctl等。打开一个文件就是调用这个文件file_operations中的open操作。不同类型的文件有不同的file_operations成员函数,如普通的磁盘数据文件,接口函数完成磁盘数据块读写操作;而对于各种设备文件,则最终调用各自驱动程序中的I/O函数进行具体设备的操作。这样,应用程序根本不必考虑操作的是设备还是普通文件,可一律当作文件处理,具有非常清晰统一的I/O接口。所以file_operations是文件层次的I/O接口。
9. linux系统调用之ioctl。
9.1内核的内存拷贝函数。copy_from_user()和copy_to_user()
主要应用于设备驱动中读写函数中,通过系统调用触发,在当前进程上下文内核态运行(即当前进程通过系统调用触发)。copy_from_user的目的是防止用户程序欺骗内核,将一个非法的地址传进去,如果没有它,这一非法地址就检测不到,内和就会访问这个地址指向的数据。因为在内核中访问任何地址都没有保护,如果不幸访问一个错误的内存地址会搞死内核或发生更严重的问题
copy_from_user调用了access_ok,所以才有“自己判断功能“access_ok(),可以检查访问的空间是否合法。
注意:1.中断代码时不能用copy_from_user,因为其调用了might_sleep()函数,会导致睡眠。2.这些内存拷贝函数不代表读,写行为,仅仅代表数据流的走向问题!
unsigned longcopy_to_user(void __user *to, const void *from, unsigned long n)
通常用在设备读函数或ioctl 中获取参数的函数中:其中“to”是用户空间的buffer地址,在本函数中将内核buffer“from”除的n个字节拷贝到用户空间的“to”buffer。
unsigned longcopy_from_user(void *to, const void __user *from, unsigned long n)
通常用在设备写函数或ioctl中设置参数的函数中:“to”是内核空间的buffer指针,要写入的buffer;“from”是用户空间的指针,数据源buffer。
9.2get_user(x,ptr)本函数的作用是获取用户空间指定地址的数值并保存到内核变量x中,ptr为用户空间的地址。用法举例如下。get_user(val, (int__user *)arg) 可以是字节,搬字,字,双字类型的内核变量 char int log 不能是结构体
注明:函数用户进程上下文内核态,即通常在系统调用函数中使用该函数。
put_user(x,ptr) 本函数的作用是将内核空间的变量x的数值保存到用户空间指定地址处,prt为用户空间地址。用法举例如下。put_user(val, (int__user *)arg)
注明:函数用户进程上下文内核态,即通常在系统调用函数中使用该函数。
9.3ioctl函数
9.3.1应用程序ioctl系统调用函数说明:
头文件:#include<sys/ioctl.h>函数原型:int ioctl(int d,int request,...); //可变参数函数
函数功能:能够利用此函数实现与设备的交互(读写)
参数说明:
fd:设备文件描述符fd,与驱动的file关联,file在跟inode关联。
request:向设备发送的命令,命令最终会赋值给驱动ioctl的第三个参数...:还可以跟一个参数。这个参数一般指定为用户空间的缓冲区首地址,也就是ioctl的第三个参数,这个参数是一个地址,他对应的就是驱动ioctl函数的第四个参数。
说明:linux内核用unsigned long这种类型是有一定道理的,对于编译器来说,unsigned long它的长度永远为4个字节,所以unsigned long变量可以存放任何值,包括地址!但是unsigned int的数据类型有可能为2个字节,也有可能是4个字节。
ioctl用户空间使用范例:
#define LED_ON 0x100001 //开灯命令 #define LED_OFF 0x100002 //关灯命令
ioctl(fd,LED_ON); //向设备发送开灯命令 ioctl(fd,LED_OFF) //向设备发送关灯命令
ioctl用三个参数: int index=1;ioctl(fd,LED_ON,&index); //向设备发送命令,并且传递给设备驱动一个用户空间缓冲求首地址
9.3.2驱动ioctl函数:
app:ioctl->sys_ioctl->led_ioctl;
int (*ioctl)(struct inode *inode,struct file *file, unsigned int cmd,unsigned long arg);
函数功能:接收用户发来的命令,并且响应处理命令
参数: inode:文件节点指针 file:文件指针以上结构体指针跟用户空间的fd关联;
cmd:保存用户ioctl发来的命令,也对应用户空间ioctl的第二个参数。
arg:它的数据类型是unsigned long型,表明arg能够存放任何值,包括地址,所以arg存放ioctl发来的用户空间缓冲区的首地址,在驱动使用时,一定要进行数据类型的转换,并且驱动不能直接访问操作arg(类似read,write的buf),如果驱动要从arg用户缓冲区中获取数据或者写入数据,必须利用内核提供的4个内存拷贝拷贝函数!
led_ioctl(...){
int index;
copy_from_user(&index,(int *)arg, 4);}
注意:linux 2.6.38.1内核版本ioctl参数改变。
long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
unlocked_ioctl函数通常用来实现原来的ioctl函数。
可以通过file间接的访问物理文件的inode,具体的实现是filp->f_path.entry->d_inode
10. linux内核提供了gpio操作的库函数。
10.1基础知识:内核为了方便驱动操作gpio,内核封装了一套的gpio操作的库函数,这些函数使用的前提是gpio为输出或者输出。并且这些库函数的底层硬件寄存器的操作都有芯片公司在平台代码中帮你实现写好没驱动只需调用库函数即可!
相关的头文件:#include <asm/gpio.h>#include <plat/gpio-cfg.h>
gpio的软件编号:芯片手册对于每一个硬件gpio,都给定一个符号“gpc1_3”或者“gpc1_3”,内核对于每一个gpio也给出了对应的软件标号(本质上就是一个数字),当然每一个gpio都有唯一的编号!
硬件标识 软件标识
gpc1_3 s5pv210_gpc1_(3)
内核提供的gpio库函数就是通过软件编号来对硬件gpio进行操作!
内核gpio口的库函数 仅仅能够配置成输入输出功能 pcm_2_sout声卡输出 i2s_2_sdo声卡的控制命令
gpio控制输出口,控制权由cpu控制 配置成输入口,控制权由外设控制,cpu不能改变gpio口的状态
10.2库函数:
gpio_request(int gpio,char *name)功能:向内核申请gpio资源 ----一旦申请成功,其他模块不能使用此硬件资源gpio:gpio软件资源name:标识(字符串)
S3C_GPIO_SFN(n):配置管脚为相关的功能口,一般n=0为输入口,n=1为输出口。
s3c_gpio_cfgpin(led_info[0].gpio, S3C_GPIO_SFN(1)); 将gpio口配置成输出口。
内核gpio资源池
软件编号S5PV210_GPA0(0)S5PV210_GPC0(3)S5PV210_GPC0(4)
//成功返回0,失败返回非0
gpio_direction_output(int gpio,int value)功能:设置GPIO为输出口,并且输出value的值
gpio:gpio软件编号 //配置成输出管教,gpio的控制权由cpu掌管value:管脚状态,高低电平
1.操作配置寄存器2.操作数据寄存器
gpio_direction_input(int gpio)功能:设置GPIO为输入口gpio:gpio软件编号 //gpio的控制权由外设来掌管
gpio_set_value(int gpio,int value)功能:设置gpio的状态为value 前提gpio口输出口
gpio_get_value(int gpio)功能:获取gpio的状态,返回值为状态信息gpio:gpio软件编号
gpio_free(int gpio//如果gpio资源不再使用,一定要归还给操作系统,供其他模块使用功能:释放gpio口资源
11.案例:linux下使用设备文件和系统调用实现led灯控制。#include <linux/init.h>
#include <linux/module.h>//驱动模块的注册
#include <linux/fs.h>//fop
#include <linux/cdev.h>//cdev
#include <linux/uaccess.h>//设备文件和设备类 内核拷贝数据
#include <asm/gpio.h> //GPIO操作
#include <plat/gpio-cfg.h>
#define LED_ON 0x100001 //定义开关的变量
#define LED_OFF 0x100002
//硬件资源
struct led_resource{
int gpio; //gpio口
char *name; //设备名称。在/proc/devices/显示
};
static struct led_resource led_info[] = {
[0] ={
.gpio= S5PV210_GPC0(3),
.name= "LED1"
},
[1] ={
.gpio= S5PV210_GPC0(4),
.name= "LED2"
}
};
//软件管理
static dev_t dev; //设备号
struct cdev led_cdev;//创建设备类
static struct class *cls; //创建设备类指针
static int led_open(struct inode *inode, struct file*file){
printk("%s\n",__func__);
return 0;
}
static int led_close(struct inode *inode, struct file*file){
printk("%s\n",__func__);
return 0;
}
static ssize_t led_read(struct file *filp, void __user*buff,
size_t count, loff_t *offp){
int state;
//从用户空间拷贝数据
copy_form_usery_form_user(&state,(int*)buff,sizeof(state));
//解析需要读取的数据
//如果等于1,获取第一个灯的状态,2为第二个灯的状态
if(state == 1)
{
gpio_get_value(S5PV210_GPC0(3),&state);
}
else if{
gpio_get_value(S5PV210_GPC0(4),&state);
}
//拷贝数据到用户空间
copy_to_user(buff,&state,sizeof(state);
}
static ssize_t led_write(struct file *filp, char __user*buff,
size_t count, loff_t *offp){
}
static int led_ioctl(struct inode *inode, struct file*file,
unsignedint cmd, unsigned long arg)
{
int kindex;
//拷贝数据到内核空间
copy_from_user(&kindex,(int*)arg, sizeof(kindex));
switch(cmd){
caseLED_ON:
gpio_set_value(led_info[kindex-1].gpio,1);
break;
caseLED_OFF:
gpio_set_value(led_info[kindex-1].gpio,0);
break;
default:
return-1;
}
return 0;
}
static struct file_operations fops ={
.owner = THIS_MOUDLE,
.open = led_open,
.release =led_close,
.read = led_read,
.write = led_write,
.ioctl = led_ioctl, //如果是linux-2.6以上的内核需要写成.unlocked_ioctl = led_ioctl,
};
static int led_init(void)
{
int i;
//1.动态的分配字符设备
alloc_chrdev_region(&dev,0, 1, "leds"); //主设备号希望从0开始分配,次设备号一个
//2.初始化字符设备
cdev_init(&led_cdev,&led_fops);
//3.注册字符设备
cdev_add(&led_drv,dev, 1);
//4.添加设备文件
//4.1主设备类 // 结果是: sys/class/myleds
cls =class_create(THIS_MOULE,"myleds");
//4.2建立设备文件
device_create(cls, NULL, dev, NULL,"myleds"); //myleds为真正的设备文件名
//5.申请gpio口资源
for(i=0; i <ARRAY_SIZE(led_info);i++){
gpio_request(led_info[i).gpio,led_info[i].name);
gpio_driection_output(led_info[i].gpio,0 );
}
return 0;
}
static void led_exit(void)
{
int i;
//删除设备文件和设备类
device_destroy(cls,dev);
class_destroy(cls);
//1.卸载字符设备对象
cdev_del(&led_cdev);
//2.释放gpio资源
for(i=0; i<ARRAY_SIZE(led_info);i++){
gpio_set_value(led_info[i].gpio,0);
gpio_free(led_info[i].gpio);
}
//3.释放设备号
unregister_chrdev_region(dev,1);
}
module_init(led_init);//驱动入口函数
module_exit(led_exit);//驱动出口函数
MODULE_LICENSE("GPL"); //GPL协议
Linux驱动入门总结相关推荐
- Linux驱动入门(四)非阻塞方式实现按键驱动
Linux驱动入门系列 Linux驱动入门(一)字符设备驱动基础 Linux驱动入门(二)操作硬件 Linux驱动入门(三)Led驱动 Linux驱动入门(四)非阻塞方式实现按键驱动 Linux驱动入 ...
- Linux驱动入门篇(一):Hello, world
学习Linux驱动有半年的时间了,但是临近毕业,由于各种事务的耽误,很多东西遗忘,现在写此博客以记录重新学习的历程. 首先,自然是从最简单的内核模块,Hello, world开始啦. 1 #inclu ...
- 【嵌入式Linux驱动入门】二、上手Hello驱动,了解驱动开发流程
我们知道他们在说谎,他们也知道他们在说谎,他们知道我们知道他们在说谎,我们也知道他们知道我们知道他们在说谎,但是他们依然在说谎. 文章目录 一.Linux驱动分类 二.Linux驱动初探 三.He ...
- 从单片机到ARM Linux驱动——Linux驱动入门篇
大一到大二这段时间里学习过单片机的相关知识,对单片机有一定的认识和了解.如果要深究其原理可能还差了一些火候.知道如何编写程序来点量一个LED灯,改一改官方提供的例程来实现一些功能做一些小东西,对IIC ...
- Linux驱动入门学习(三):I2C架构全面理解
I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...
- Linux驱动入门基础基础知识
嵌入式折腾了如此之久,今天终于有幸的点亮了第一个LED--故记录之 在Linux下,驱动可以分为三大类,字符设备,块设备,和网络接口. 这次完成LED驱动的就是的字符类设备(character dev ...
- Linux驱动入门(三)——源码下载阅读、分析和嵌入式文件系统介绍
文章目录 从内核出发 获取内核源码 使用Git 安装内核源码 使用补丁 阅读Linux内核源码 Source Insight简介 阅读源码 内核开发的特点 无libc库抑或无标准头文件 GNU C 没 ...
- linux驱动导出文件属性,linux驱动入门——模块参数和导出符号
驱动程序中lsmod命令实际读取的是/proc/modules文件即与lsmod命令对应的结果是cat /proc/modules内核中已经加载的模块的信息存在于/sys/module目录下modpr ...
- linux 驱动入门 魏清,Linux下的SPI总线驱动(三)
版权所有,转载请说明转自 原创作者:南京邮电大学 通信与信息系统专业 研二 魏清 五.SPI测试代码 对于SPI总线驱动,我们可以分为SPI控制设备驱动和SPI接口设备驱动.而作为驱动开发人员主要是 ...
- linux添加hello驱动,Linux驱动之建立一个hello模块
目标:在开发板上执行insmod hello.ko能在控制台打印出hello init:接着执行rmmod会在控制台打印出hello exit 建立一个hello模块的步骤如下: 1.建立一个hell ...
最新文章
- Colly源码解析——框架
- jquery 地理位置 与 IP地址
- 赴马来西亚旅游遇车祸 70岁中国籍老人不幸身亡
- 通过telnet连接查看memcache服务器
- div里面放ul,使ul横向和纵向滚动
- 北京化工大学2018年10月程序设计竞赛部分题解(A,C,E,H)
- Linux 小知识翻译 - 「/proc 文件夹」
- drools 7.11.0.Final使用
- docker项目部署 php_使用Docker部署PHP开发环境的方法详解
- 构建高可用linux和鸟哥,构建高可用Linux服务器
- Quill – 可以灵活自定义的开源的富文本编辑器
- LeetCode:Minimum Window Substring
- MediaCodec解析MP4视频
- 扫描仪没有linux 驱动怎么安装,扫描仪驱动安装不上怎么办_扫描仪驱动安装方法 - 驱动管家...
- 一次网络丢包问题排查的经历
- 深度Linux怎样关闭休眠,deepin如何休眠,
- 易捷行云获选国际开源基础设施基金会OIF“双董事” 席位
- hⅰgh怎么读音发音英语_gh和ph的发音
- 解决车载U盘:USB设备未连接 问题
- JavaScript颜色加深或减淡