linux设备驱动--LED驱动
最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步。
作者:liufei_learning(转载请注明出处)
email:flying0216@foxmail.com
IT学习交流群:160855096
开发环境:Win7(主机) +VisualBox + ubuntu10.10(虚拟机) + TQ2440开发板(2.6.30.4内核)
功能:实现tq2440下led控制以及自动创建设备节点
目录:
1.实现
1)程序分析框图:
2)程序流程
3)原理图
2.步骤
3.分析
1)字符设备注册问题
2)file_operations()
3)ioctl
4)设备节点的自动创建
实现:
程序分析框图:
|
函数
fun:leds_open()
fun:leds_ioctl()
fun:leds_init()
fun:leds_exit()
结构体
struct:led_table
struct:led_cfg_table
struct:led_fops
|
调用
控制程序执行open()时,会调用此函数
控制程序执行ioctl)时,会调用此函数
加载模块时会调用此函数
卸载函数时会调用此函数
功能
控制LED的IO口
LEDIO口的模式
注册字符设备时,通过此结构体关联leds_open(),Leds_ioctl()
|
程序流程
|
首先通过入口函数module_init(leds_init),进入leds_init()进行初始化操作,设置GPIO口,注册字符设备,通过led_fops结构体关联leds_open(),Leds_ioctl(),创建设备节点,卸载时调用leds_exit()注销设备删除设备节点
|
由原理图得知LED电路是共阳极的,并分别由2440的GPB5、GPB6、GPB7、GPB8口控制
步骤:
(参考http://hbhuanggang.cublog.cn,添加自动加载设备节点
)
1.去掉内核已有的LED驱动设置,因为IO口与tq2440开发板不一致 修改arch/arm/plat-s3c24xx/common-smdk.c
2.编写tq2440的LED驱动,代码如下
3.编写应用程序测试LED驱动
4. 把LED驱动代码部署到内核中去
#cp -f tq2440_leds.c/linux-2.6.30.4/drivers/char //把驱动源码复制到内核驱动的字符设备下
#vim /linux-2.6.30.4/drivers/char/Kconfig //添加LED设备配置
#vim /linux-2.6.30.4/drivers/char/Makefile //添加LED设备配置
obj-$(CONFIG_TQ2440_LEDS)+=tq2440_leds.o
5. 配置内核,选择LED设备选项
#make menuconfig
Device Drivers --->
Character devices --->
<*>TQ2440 Leds Device (NEW)
6.编译内核make zImage
7.编译测试程序#arm-linux-gcc -o led_test led_test.c 将生成的文件复制到开发板 /usr/sbin下
8.开发板上测试
查看已加载的设备:#cat/proc/devices,可以看到tq2440_leds的主设备号为231
控制led输入led_test on 1可以看到对应led被点亮
分析:
1.字符设备注册问题
register_chrdev()
intregister_chrdev(unsigned int major, const char *name,
const structfile_operations *fops)
register_chrdev()为字符设备注册一个主设备号
参数:majar:主设备号若==0,则系统动态分配一个主设备号,>0系统尝试用所给的值存储主设备号
成功返回0,失败返回错误errno:-ve
name:设备名称
fops:与此设备相关的文件操作
设备号的范围为:0~255
2.6内核以后的大量驱动代码, 有许多字符驱动不使用上面代码的方法. 那是还没有更新到 2.6 内核接口的老代码. 因为那个代码实际上能用,这个更新可能很长时间不会发生. 为完整, 我们描述老的字符设备注册接口, 但是新代码不应当使用它; 这个机制在将来内核中可能会消失(LDD3),所以下一篇blog将讨论新的方法。不过老的方法我觉得还是有必要学习一下,了解系统实现的原理,有助于跟深入的理解字符设备驱动。
在Linux中,字符设备是用一个叫做字符设备结构的数据结构chardevicestruct来描述的。为了管理上的方便,系统维护了一个数组chrdevs[],该数组的每一项都代表一个字符没备。
在文件linux/fs/char_dev.c中定义的char_device_struct的数据结构及数组chrdevs[]代码如下:
结构中的一个域name是指向设备驱动程序名的指针;另一个域fops是指向-个封装了文件操作函数集结构的指针。这些文件操作函数就是对这个字符设备进行具体的如打开、读、写、关闭等文件操作驱动程序。
字符设备注册表结构如图所示。当安装一个字符设各时,须调用注册函数regesterchardev()向注册表插入一个新的表项。函数regester_chardev()的原型如下:
图字符设备驱动程序的注册
当代表-个字符设备的文件被进程打开时,系统根据设备主、次设各号,查询上述的chrdevs[]数组,并获得fops指针和为进程设置-个描述这个字符特眯文件跑数握结构file,进而通过fops指针调用指定的驱动程序。
取消注册的函数为unregister_chrdev()。其原型如下:
intunregister_chrdev int major,const char*name):
设备驱动程序的注册和取消注册应分别在模块的初始化函数和析构函数中完成。
2.file_operations()
struct module *owner
第一个file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载.几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.
loff_t (*llseek) (structfile *, loff_t, int);
llseek方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file结构中的位置计数器( 在"file 结构" 一节中描述).
ssize_t (*read) (structfile *, char __user *, size_t, loff_t *);
用来从设备中获取数据.在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败.一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
ssize_t(*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一个异步读-- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
ssize_t (*write) (structfile *, const char __user *, size_t, loff_t *);
发送数据给设备.如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
ssize_t(*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化设备上的一个异步写.
int (*readdir) (structfile *, void *, filldir_t);
对于设备文件这个成员应当为NULL; 它用来读取目录, 并且仅对文件系统有用.
unsigned int (*poll)(struct file *, struct poll_table_struct *);
poll方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll方法为 NULL, 设备假定为不阻塞地可读可写.
int (*ioctl) (structinode *, struct file *, unsigned int, unsigned long);
ioctl系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.
int (*mmap) (struct file*, struct vm_area_struct *);
mmap用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
int (*open) (struct inode*, struct file *);
尽管这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
int (*flush) (struct file*);
flush操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前,flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL,内核简单地忽略用户应用程序的请求.
int (*release) (structinode *, struct file *);
在文件结构被释放时引用这个操作.如同 open, release 可以为 NULL.
int (*fsync) (struct file*, struct dentry *, int);
这个方法是fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.
int (*aio_fsync)(structkiocb *, int);
这是fsync 方法的异步版本.
int (*fasync) (int,struct file *, int);
这个操作用来通知设备它的FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.
int (*lock) (struct file*, int, struct file_lock *);
lock方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.
ssize_t (*readv) (structfile *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (structfile *, const struct iovec *, unsigned long, loff_t *);
这些方法实现发散/汇聚读和写操作.应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和write 方法被调用( 可能多于一次 ).
ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
这个方法实现sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用.设备驱动常常使 sendfile 为 NULL.
ssize_t (*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);
sendpage是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.
unsigned long(*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsignedlong, unsigned long);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.
int (*check_flags)(int)
这个方法允许模块检查传递给fnctl(F_SETFL...) 调用的标志.
int (*dir_notify)(structfile *, unsigned long);
这个方法在应用程序使用fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.
3.ioctl
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:
intioctl(int fd, ind cmd, …);
其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。
用法很简单就是通过switch解析命令
4.设备节点的自动创建
在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。
内核中定义了structclass结构体,顾名思义,一个structclass结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。通过device_destroy();class_destroy();来注销类和节点
分享到:
相关推荐
基于最基本的Linux LED字符设备进行改进,在原来的基础上将驱动进行分层设计,拆分出通用的部分作为系统级驱动,剩余的跟芯片相关的部分作为芯片级驱动,以后...体现Linux驱动分层设计思想,以及面向对象的接口抽象。
在Linux操作系统中,设备驱动是连接硬件和操作系统内核的关键组件...这个实例不仅涵盖了GPIO操作,还可能涉及中断处理、系统调用、设备模型等核心概念,对于想要学习Linux驱动开发的工程师来说是非常有价值的实践案例。
LED驱动程序主要是为了使得Linux内核能够识别和管理硬件上的LED,通常涉及GPIO(General Purpose Input/Output)接口的使用。GPIO是许多微控制器和SoC(System on Chip)上的一种通用接口,可以配置为输入或输出,...
博客http://blog.csdn.net/qq_30951423/article/details/76071333中的字符设备驱动,调试通过的。
标题"led-drvier.rar_2410_2410 linux 驱动_led-2.6.12_led-drvier"表明这是一个针对S3C2410处理器的Linux驱动程序,具体为LED驱动,适用于Linux内核版本2.6.12。这个驱动程序的目的是管理并控制硬件上的LED灯,使...
LED驱动:GEC6818开发板上的灯驱动 各种版本的mplaye: 音视频播放器,使用命令即可播放,支持win10和开发板,应用于各种场合,特别是语音识别技术,很多毕设都是用这个做的。 gcc编译工具:arm+linux-gnueabi-5.4.0....
通过理解和实现这些知识点,我们可以有效地编写和管理LED驱动,让硬件设备在Linux系统中发挥应有的作用。在提供的压缩包文件“s3c2440 driver for led”中,很可能包含了具体的驱动源代码,通过分析这些代码,我们...
《Linux设备驱动开发详解:基于最新的Linux4.0内核配套光盘》一书由宋保华编写,针对Linux4.0内核进行了深入浅出的解析与实践指导。本书结合了理论与实践,旨在帮助读者掌握Linux设备驱动程序的设计与开发技能。 ##...
- **内核与驱动**: 分析LDD6410内核和BSP配置,学习按键、LED、LCD、声卡(ASoC)、网卡(DM9000)、USB等设备驱动开发方法。 - **根文件系统**: 构建和使用nfs作为根文件系统,便于开发过程中的系统管理和文件访问。 - ...
结合本作者博客 Linux简单设备驱动(1):使用IO内存操作GPIO–LED 的源代码
总的来说,《嵌入式Linux设备驱动程序开发指南》为读者提供了一个深入理解嵌入式Linux驱动开发的全面指南,结合理论与实践,有助于提升开发者在这个重要领域的专业技能。无论是初学者还是经验丰富的工程师,都能从中...
这个示例不仅涵盖了基本的Linux驱动程序开发知识,还涉及到了中断处理、GPIO操作等内容,对于初学者来说是一个很好的入门项目。在实际应用中,可以根据不同的硬件平台和需求进行相应的调整和优化。
LED驱动属于字符设备,因为它不涉及连续的数据流,而是简单的开关操作。理解设备驱动的注册、初始化、数据传输和卸载过程是驱动设计的核心。 4. **LED驱动编程**:LED驱动程序的基本任务是控制GPIO(General-...
总结来说,这个"led字符设备Linux驱动代码"的学习将涵盖以下几个核心知识点: 1. Linux内核驱动程序基础 2. C语言在驱动开发中的应用 3. Linux字符设备驱动模型 4. 文件操作结构体`file_operations` 5. 设备注册和...
在Linux系统中,LED驱动通常作为字符设备驱动实现,通过`/dev`节点与用户空间交互。 2. **字符型设备驱动**: 字符型设备驱动用于处理非块设备,如串口、键盘或LED等。在Linux内核中,它通过注册到字符设备模型来...
【Linux驱动开发】OK6410系列之03---LED字符设备驱动 在Linux驱动开发中,LED字符设备驱动是初学者常见的实践项目,它能够帮助开发者理解基本的硬件控制和驱动程序工作原理。本篇文章以OK6410开发板为例,详细讲解...
本压缩包“Linux字符设备驱动LED源码”包含了在S5PV210处理器平台上实现LED驱动的具体代码和一种用于数据传输的管道缓存算法解析。 S5PV210是一款由Samsung制造的ARM Cortex-A8微处理器,常用于嵌入式系统和移动...
对于想对Linux驱动开发入门的同学来说,理解和掌握设备驱动程序的工作原理以及如何编写它们是至关重要的。 在Linux系统中,设备驱动程序可以分为字符设备、块设备和网络设备等类型。字符设备处理单个数据流,如键盘...