完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
【EVB-335X-II试用体验】之基于平台设备驱动模型的GPIO输出驱动开发(含源码) 在前面两篇EVB-335X_II的试用报告中,介绍了基于物理寄存器内存地址映射到内核空间开发驱动程序,以及基于GPIO内核模型的驱动开发。 本篇试用报告主要介绍一下基于平台、设备驱动模型的GPIO驱动程序开发,通过了解平台设备驱动模型的原理、驱动编写流程、编译驱动、加载驱动、调试,以及编写测试程序,逐步掌握简单的平台设备驱动模型开发驱动程序的一般方法,为今后研究更高级的SPI、IIC等设备驱动程序,打下一个良好的基础。 1. 平台设备、驱动模型概述 从Linxu2.6 内核开始,引入了 platform 机制,能够实现对设备所占用的资源进行统一管理。 Platform机制抽象出了 platform_device 和 platform_driver 两个核心概念。 platform device负责管理或描述该设备具有的资源,比如该设备相关寄存器的物理起始地址及范围,所占用的中断号,平台设备以一个独立的内核模块存在,执行insmod命令后,系统将该设备的结构指针链接入platform bus设备总线中。 而plateform diver,机平台驱动,对适合某一类设备的驱动,把这些类型的驱动抽象出来,让这些具有不同的其实地址和地址空间,但是他们的驱动方法是一致的,同一类的设备设备共享相同的驱动程序,从而简化设备驱动程序的开发,也减轻了内核驱动维护的压力。 在platform device和platform drvier中间,有一个叫做总线的层,我们这里指的是platform bus。当执行insmod 设备模块时,内核函数将platform device信息链接入总线控制的设备链表;当执行insmod 驱动模块时,内核函数将plateform driver信息链接入总线控制的驱动链表。 对于插入设备模块时,内核提取该模块的名字或ID信息与该设备所属的平台总线所管理的驱动链表中的每元素进行比较,如果发现有设备与该驱动名称或ID匹配,则执行该匹配的驱动程序的probe函数(探测函数),以根据相匹配的设备描述信息(该设备的资源)对设备进行初始化,进而申请主设备号、创建类,创建设备等一系列的流程。 对于插入平台驱动模块时,内核根据该驱动模块名字或ID,到该驱动模块所属的平台所管理的设备链表查找,看是否有与之匹配的设备,如果有,则将设备的devic信息传递给当前正在加载的驱动模块的probe函数,使其完成对设备号、设备在/dev目录下对应文件的创建等工作。 总线之所以能完成上面所述的匹配、探测工作,主要其两个关键数据结构链表和一个匹配比较函数,即:该bus下面的设备链表和驱动链表,以及一个match函数。 基于以上的分析,我们开发基于平台设备的驱动程序,需要两个模块,设备模块和驱动模块,在设备模块中定义资源的描述信息,在驱动模块中去向系统申请、配置这些资源,以向应用层提供一个访问这些硬件资源的公共接口。 2. LED平台设备 LED平台设备定义描述LED资源信息,并通过设备模块的注册流程,将想设备添加的平台总线。 LED平台设备的资源主要包括其对应的GPIO编号。 2.1 LED平台设备源码 源码如下: #include #include #include #include #define GPIO_LED_PIN_NUM (32*3 + 6) /* gpio3_16 */ /* 定义 LED 资源 */ static struct resource led_resources[] = { [0]= { .start= GPIO_LED_PIN_NUM, .end= GPIO_LED_PIN_NUM, .flags= IORESOURCE_IO, }, }; static void led_platform_release(structdevice *dev) { return; } /* 定义平台设备 */ static struct platform_deviceled_platform_device = { .name= "led", /* platform_driver 中, .name 必须与该名字相同 */ .id= -1, .num_resources= ARRAY_SIZE(led_resources), .resource= led_resources, .dev= { /*Device 'led' does not have a release() function, it is broken and must befixed. */ .release= led_platform_release, .platform_data= NULL, }, }; /*平台设备模块初始化函数*/ static int __init led_platform_init(void) { intret; ret= platform_device_register(&led_platform_device); if(ret < 0) { platform_device_put(&led_platform_device); returnret; } return0; } /*模块退出函数*/ static void __exit led_platform_exit(void) { platform_device_unregister(&led_platform_device); } /*模块装置函数*/ module_init(led_platform_init); /*模块卸载函数*/ module_exit(led_platform_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("lzp, I come fromhttps://bbs.elecfans.com/"); 2.2 LED平台设备Makefile KERN_DIR = /share/EVB_335X_II/linux/linux-3.14.43-evb335x-ii all: make-C $(KERN_DIR) M=`pwd` modules clean: make-C $(KERN_DIR) M=`pwd` modules clean rm-rf modules.order obj-m +=led_platform.o 其中内核路径和模块名称与你自己的相匹配。 2.3 交叉编译LED平台设备模块 输入如下命令: sudo make ARCH=ARM CROSS_COMPILE=/交叉编译器路径/bin/arm-linux-gnueabihf- 编译结果如图所示: 3. LED平台驱动 LED的平台驱动结合平台设备定义资源的一般规则,在probe函数中获取总线函数传递过来的设备资源信息,申请资源,创建主设备号、类以及device。 3.1 LED平台驱动头文件 为了是LED平台设备能够支持标准的ioctl命令规则,我们专门设计了一个头文件,对命令的合成规则做一约定,代码如下: #ifndef _LED_DRV_H #define _LED_DRV_H #define LED_IOC_MAGIC 'L' #define LED_ON _IO(LED_IOC_MAGIC, 0) #define LED_OFF _IO(LED_IOC_MAGIC, 1) #define LED_IOCTL_MAXNR 2 #endif /*_LED_DRV_H*/ 3.2 LED平台驱动源文件 #include #include #include #include #include #include #include #include #include "../led_drv.h" static int major; static int minor; struct cdev *led; /* cdev 数据结构 */ static dev_t devno; /* 设备编号 */ static struct class *led_class; static int led_io; /* 用于保存 GPIO 编号 */ #define DEVICE_NAME "led" /*字符设备open函数,应用层文件被打开时被调用*/ static int led_open(struct inode *inode,struct file *file ) { try_module_get(THIS_MODULE); gpio_direction_output(led_io,1); return0; } /*字符设备关闭时调用的release函数*/ static int led_release(struct inode *inode,struct file *file ) { module_put(THIS_MODULE); gpio_direction_output(led_io,1); return0; } /*对应ioctl函数,Linux2.6.36版本之前和之后的函数参数个数不一样,所以我们加入内核版本的判断*/ #if LINUX_VERSION_CODE >=KERNEL_VERSION(2,6,36) int led_ioctl(struct file *filp, unsignedint cmd, unsigned long arg) #else static int led_ioctl(struct inode *inode,struct file *file, unsigned int cmd, unsigned long arg) #endif { if(_IOC_TYPE(cmd) != LED_IOC_MAGIC) { return-ENOTTY; } if(_IOC_NR(cmd) > LED_IOCTL_MAXNR) { return-ENOTTY; } switch(cmd){ caseLED_ON: gpio_set_value(led_io,1); break; caseLED_OFF: gpio_set_value(led_io,0); break; default: gpio_set_value(led_io,0); break; } return0; } struct file_operations led_fops = { .owner= THIS_MODULE, .open= led_open, .release= led_release, #ifLINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36) .unlocked_ioctl= led_ioctl #else .ioctl= led_ioctl #endif }; static int __init led_probe(structplatform_device *pdev) { intret; structresource *res_io; res_io= platform_get_resource(pdev, IORESOURCE_IO, 0); /* 从设备资源获取 IO 引脚 */ led_io= res_io->start; ret= alloc_chrdev_region(&devno, minor, 1, DEVICE_NAME); /* 从系统获取主设备号 */ major= MAJOR(devno); if(ret < 0) { printk(KERN_ERR"cannot get major %d n", major); return-1; } led= cdev_alloc(); /* 分配 led 结构 */ if(led != NULL) { cdev_init(led,&led_fops); /* 初始化 led 结构 */ led->owner= THIS_MODULE; if(cdev_add(led, devno, 1) != 0) { /* 增加 led 到系统中 */ printk(KERN_ERR"add cdev error!n"); gotoerror; } }else { printk(KERN_ERR"cdev_alloc error!n"); return-1; } led_class= class_create(THIS_MODULE, DEVICE_NAME"_class"); if(IS_ERR(led_class)) { printk(KERN_INFO"create class errorn"); return-1; } device_create(led_class,NULL, devno, NULL, DEVICE_NAME); return0; error: unregister_chrdev_region(devno,1); /* 释放已经获得的设备号 */ returnret; } static int __exit led_remove(structplatform_device *dev) { cdev_del(led);/* 移除字符设备 */ unregister_chrdev_region(devno,1); /* 释放设备号 */ device_destroy(led_class,devno); class_destroy(led_class); return0; } /* 定义和初始化平台驱动 */ static struct platform_driverled_platform_driver = { .probe= led_probe, .remove= led_remove, .driver= { .name= "led", /* 该名称必须与 platform_device 的.name 相同 */ .owner= THIS_MODULE, }, }; static int __init led_init(void) { return(platform_driver_register(&led_platform_driver)); } static void __exit led_exit(void) { platform_driver_unregister(&led_platform_driver); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("lzp, I come fromhttps://bbs.elecfans.com/"); 3.3 LED平台驱动Makefile KERN_DIR = /share/EVB_335X_II/linux/linux-3.14.43-evb335x-ii all: make-C $(KERN_DIR) M=`pwd` modules clean: make-C $(KERN_DIR) M=`pwd` modules clean rm-rf modules.order obj-m +=led_drv.o 其中内核路径和模块名称与你自己的相匹配。 3.4 交叉编译LED平台驱动模块 输入如下命令: sudo make ARCH=arm CROSS_COMPILE=/交叉编译器路径/bin/arm-linux-gnueabihf- 编译结果如图所示: 4 测试程序 编写一个用户层的测试应用程序,是LED以2S的周期闪烁。程序源码如下: #include #include #include #include #include #include #include "../led_drv.h" #define DEV_NAME "/dev/led" int main(int argc, char *argv[]) { inti; intfd = 0; fd= open (DEV_NAME, O_RDONLY); if(fd < 0) { perror("Open"DEV_NAME" Failed!n"); exit(1); } while(1){ ioctl(fd,LED_ON); sleep(1); ioctl(fd,LED_OFF); sleep(1); } close(fd); return0; } 执行如下命令编译: /交叉编译器路径/bin/arm-linux-gnueabihf-gcc -o led_test led_test.c 编译结果如下: 5 测试驱动 将上述交叉编译的设备模块、驱动模块以及测试程序拷贝到NFS共享目录。启动开发板,设置IP地址: ifconfig eth0 192.168.1.102 挂载NFS文件系统: mount -t nfs 192.168.1.103:/nfsshare /mnt-o nolock 执行命令加载模块: cd /mnt insmod led_platform.ko insmod le_drv.ko ls /dev/le* ./led_test 命令行显示如下信息: 开发板上的LED显示,如图所示: |
|
相关推荐
2 个讨论
|
|
只有小组成员才能发言,加入小组>>
【盈鹏飞RK3399安卓主板 XPC-3399Pro免费试用】+烧写出厂固件
10279 浏览 0 评论
【盈鹏飞EVB-T335开发板试用体验】debian系统烧写
3300 浏览 1 评论
【盈鹏飞I.MX6UL工控开发板试用体验】linux can 测试
3194 浏览 0 评论
268浏览 0评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 21:13 , Processed in 1.906057 second(s), Total 70, Slave 52 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号