历史上的今天
今天是:2025年03月10日(星期一)
2020年03月10日 | Linux内核模块驱动之---led驱动
2020-03-10 来源:eefocus
开发板:tiny6410
内核:linux2.6.38
要写led驱动首先要复习的是混杂设备驱动的设计流程、 内核file_ops结构体、硬件通信IO端口和IO内存、 ioctl系统调用的实现步骤和方法
按照顺序来对已经学习的内容复习 并把led模块驱动写出来
第一步:复习ioctl系统调用函数
一、什么是ioctl系统调用函数,驱动中的iodtl函数是什么样子的?为什么需要ioctl函数?》
虽然file_ops结构体提供了相当多的文件操作函数,但是要想对硬件操作,这个file_ops结构体无法完成了,所以操作系统又提供了另一个对硬件操作的函数,就是ioctl,ioctl分为在应用层 和 在驱动层。ioctl应用层的函数传递命令,从用户态将命令传递到内核态,调用驱动的ioctl函数来实现对硬件的操作。
上图::从网上找的
用户空间中的ioctl 和 驱动的ioctl有点不一样
用户空间的ioctl函数形式为:
int ioctl(int fd, unsigned long cmd, ...);//参数的意义:::第一个代表:文件描述符,第二个代表命令 第三个为可选的参数
驱动中的ioctl从2.6.36开始改名称了-----改为unlocked_ioctl
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
参数意义:第一个 为用户空间传递下来的 文件指针,
第二个为参数的命令 cmd这个命令的定义和获取 接下来要详细的讲解一下
第三个为 用户空间传递 的 可选的参数
二、在写ioctl代码之前首先要搞懂如何定义命令,为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。。。
ioctl命令编码被划分为几个段,在include/asm/ioctl.h 中定义了这些字段:类型(幻数),基数,传送方向,参数大小等。在Documentation/ioctl-number.txt文件中 罗列了在内核中已经使用的幻数。
定义ioct命令的正确方法是 使用四个位段::
Type(8)-------------类型(幻数)表明是哪个设备的命令,要参考Documentation/ioctl-number.txt文档后钻出合适的命令
Number(8)----------序号,表明设备命令中的第几个,8位宽
Direction(2)--------数据传送的方向,可能的值是 ———_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE.数据的传送方向是从应用程序的观点来看待的,_IOC_READ意为从设备中读取数据
Size(8~14)----------用户数据的大小(8-14位宽,视处理器而定)。
上面是对一个命令所包含信息的解析,一个命令应该包含什么信息。
那么如何定义一个命令呢,内核提供了下列宏来定义命令
_IO(type,nr);//没有参数的命令
_IOR(type,nr,datatype);//从驱动中读数据
_IOW(type,nr,datatype);//向驱动中写入数据
_IOWR(type,nr,datatype);//双向传送
内核还定义了用来解开宏的字段
_IOC_DIR(nr);
_IOC_TYPE(nr);
_IOC_NR(nr);
_IOC_SIZE(nr); nr为要解析的命令
三、ioctl函数中如何实现命令
1、一般在ioctl函数的实现通常是根据命令执行的一个switch语句。但是当命令不能匹配任何一个设备所持有的命令的时候,通常返回-EINVAL.
2、ioctl函数的第三个参数arg的使用
如果arg是一个整数,那么可以直接使用,如果是指针,我们必须确保这个用户地址是有效的,因此使用前需要进行正确性检查,使用下面的函数对arg参数的正确性进行检查
int access_ok(int type, const void* addr, unsigned long size);//第一个是类型 参数为VERIFY_READ 或者VERIFY_WRITE,用来表明读用户内存 还是 写用户内存。addr参数是要操作的用户内存地址,size 是操作的长度。
如果需要同时读写 则使用参数VERIFY_WRITE。
access_ok()返回一个布尔类型的值:1 是成功(存取没问题) 0是失败(存取有问题)。
第二步:复习file_ops结构体
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);//文件定位函数的实现
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//文件的读取函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//文件的写入函数
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);//读取文件夹
unsigned int (*poll) (struct file *, struct poll_table_struct *);//对应select系统调用
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//硬件操作函数
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);//内存映射
int (*open) (struct inode *, struct file *);//打开
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);//文件释放
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
在这个结构体中并不是所有的函数都需要实现,只实现需要的函数即可。
第三部:复习硬件操作--IO内存和IO端口
真正要实现设备的操作时必须要设计到硬件的操作的,设备驱动程序是软件概念和硬件概念的一个抽象层,因此都要讨论。
每种外设都通过寄存器来进行控制。大部分外设都设有几个寄存器,不管是在内存地址,还是在IO地址空间,这些寄存器的访问地址都是连续的。
在硬件层,内存区域和IO区域没有概念上的区别:他们都是通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。
详细请参考下面的连接::
IO端口、IO内存详细解析
第四步:混杂设备驱动程序设计
在linux系统 中,存在一类字符设备,他们共享一个主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice),所有混杂设备形成一个链表,对设备访问时内核根据次设备号查找到相应的miscdevice设备。
描述混杂设备的结构体:
struct miscdevice {
int minor;//次设备号--一般是有设备自动分配的--MISC_DYNANIC_MINOR
const char *name;//设备名称
const struct file_operations *fops;//设备对应的 文件操作集
struct list_head list;//
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
如何使用混杂设备:
在初始化的时候注册 混杂设备就好
int misc_register(struct miscdevice * misc);
在模块卸载的时候将混杂设备注销:
misc_deregister(struct miscdevice *misc);
上LED模块驱动的例程:::
/**************************************************************************/
/***************************led.h**********************************************/
#ifndef _LED_H_
#define _LED_H_
#include /*涉及到ioctl命令的定义方法的问题*/ #define DEVICE_NAME "tiny6410-led" /**/ #define LED_IOC_MAGIC 'l'/*定义命令类型*/ /**/ #define LED_IOCGETDATA _IOR(LED_IOC_MAGIC,1,int)/*利用宏来辅助定义命令--定义从驱动中获得数据*/ #define LED_IOCSETDATA _IOW(LED_IOC_MAGIC,2,int)/*利用宏来辅助定义命令--定义向驱动中写入数据*/ #define LED_IOC_MAXNR 2 #endif /**************************************************************************/ /***************************led.c**********************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include /*要了解各个头文件中所包含的函数*/ #include"led.h" /*led对应GPIOK虚拟地址*/ unsigned long GPIOK_VA_BASE; #define GPIOK_CON0_VA GPIOK_VA_BASE #define GPIOK_CON1_VA GPIOK_VA_BASE+0x4 #define GPIOK_DAT_VA GPIOK_VA_BASE+0x8 #define GPIO_PUD_VA GPIOK_VA_BASE+0xc /*led对应的物理地址*/ #define GPIOK_PA_BASE 0x7f008800 /*linux 采用resource描述挂接在cpu总线上的结构实体*/ struct resource tiny6410_led_resource = { .name = "led io-mem", .start = GPIOK_PA_BASE, .end = GPIOK_PA_BASE + 0x10, .flags = IORESOURCE_MEM, }; static void tiny6410_led_pin_setup(void) { unsigned long start = tiny6410_led_resource.start; unsigned long size = tiny6410_led_resource.end - tiny6410_led_resource.start; unsigned long tmp; /*申请io内存*/ request_mem_region(start,size,tiny6410_led_resource.name); /*映射io内存*/ GPIOK_VA_BASE = (unsigned long)ioremap(start,size);//映射的地址 由系统自动分配 printk("映射的虚拟地址 GPIOK_VA_BASE = 0x%lxn",GPIOK_VA_BASE); /*对应管教设置为输出*/ tmp = readl(GPIOK_CON0_VA); tmp = (tmp & ((0xffffU<<16)|(0x1111U<<16))); writel(tmp,GPIOK_CON0_VA); /*对应管脚置高--使led全灭*/ tmp = readl(GPIOK_DAT_VA);//将寄存器在内存中的虚拟地址的数据读出来 tmp |= (0xf<<4); writel(tmp,GPIOK_DAT_VA); } /****************************************************/ static void tiny6410_led_pin_release(void) { /**首先要解除映射**/ iounmap((void*)GPIOK_VA_BASE);//参数是 映射到内存的地址 release_mem_region(tiny6410_led_resource.start,tiny6410_led_resource.end - tiny6410_led_resource.start); } /**************************************************************************************************/ static unsigned long tiny6410_led_getdata(void) { return ((readl(GPIOK_DAT_VA)>>4)&0xF);//返回虚拟io内存中寄存器的值 } /*设置led对应的GPIO数据寄存器的值*/ static void tiny6410_led_setdata(int data) { unsigned long tmp; tmp = readl(GPIOK_DAT_VA); tmp = ~(0xF<<4) | ((data&0xF)<<4); writel(tmp,GPIOK_DAT_VA); } /*****************************************************************************************************/ static long led_ioctl(struct file* filp,unsigned int cmd,unsigned long arg) { int ioarg,ret; /*检测命令的有效性*/ if(_IOC_TYPE(cmd) != LED_IOC_MAGIC)//检测命令 return -EINVAL; if(_IOC_NR(cmd) > LED_IOC_MAXNR)//检测命令号 如果大于最大的命令号 return -EINVAL; /*根据命令来执行不同的操作*/ switch(cmd) { case LED_IOCGETDATA:{ ioarg = tiny6410_led_getdata(); ret = put_user(ioarg,(int*)arg); break; } case LED_IOCSETDATA:{ ret = get_user(ioarg,(int*) arg); tiny6410_led_setdata(ioarg); break; } default: return -EINVAL; } return ret; } static ssize_t led_read(struct file* filp,char __user* buf,size_t size,loff_t *ppos) { } /********************************************************************************************/ static struct file_operations dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl = led_ioctl, .read = led_read, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR,//次设备号由系统动态分配 .name = DEVICE_NAME, .fops = &dev_fops,//文件操作 }; /****************************************************************************************/ static int __init dev_init(void) { int ret; tiny6410_led_pin_setup();//在板子初始化的时候 要将寄存器映射到内存中去 ret = misc_register(&misc); printk("initialized minor =%dn",misc.minor); return ret; } static void __exit dev_exit(void) { tiny6410_led_pin_release(); misc_deregister(&misc);//混杂字符设备驱动的解除 } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("kong-hua-sheng"); /************************************************************************************/ /***********************************测试程序app-led.c************************************************/ #include #include #include #include #include #include #include #include"led.h" #define DEV_NAME "/dev/"DEVICE_NAME /*将传送进来的字符转换为int型数据*/ int binstr_to_int(char* binstr) { int ret =0,i=0; char bnum[5]; memset(bnum,'0',4); int len = strlen(binstr); if(len>4) { strcpy(bnum,binstr+len-4); }else { strcpy(bnum+4-len,binstr); } for(i=0;i<4;i++) { ret <<=1; ret +=(bnum[i]=='0'? 1:0); } return ret; } int main(int argc,char* argv[]) { if(argc>2) { printf("can shu > 2 error!!n"); _exit(EXIT_FAILURE); } int fd,ioarg; if(-1 == (fd = open(DEV_NAME,O_RDWR))) { ioarg = ioctl(fd,LED_IOCGETDATA,&ioarg); printf("canshu wei 1 shi duqu shu ju! %d n",ioarg); }else{ ioarg = binstr_to_int(argv[1]); ioctl(fd,LED_IOCSETDATA,&ioarg); } _exit(EXIT_SUCCESS); } /**********写好之后如何测试命令******************/ #arm-linux-gcc -o app_led app-led.c #./app_led 1101 这样led就会根据你设置的数字来点亮对应的发光二极管
史海拾趣
|
利用金属底壳,加垫MAP软性硅胶导热片达到散热效果。 原来大家使用导热硅脂作为导热材料的很多: 当今的电子产品朝着两个方向发展:一方面产品的集成度越来越高、功耗不断增大;另一方面产品越来越轻、薄、短。这就使得产品的散热矛盾越来越突出 ...… 查看全部问答> |
|
在运行TOPWIN2005的时候出现了EAcess violation的错误,我点了确定后显示 Acess violation at address 740b0cc2,read of address 740b0cc2,是怎么回事? 我是第一次用TOPWIN2005,不好意思! 请哪位大侠帮个忙,小妹我不胜感激!… 查看全部问答> |
|
第一节: 心情和时钟 说实话我能够使用的单片机不多,我总是以为无论什么单片机都能开发出好的产品。 前些年用51,总是向各位大大学习,无休止的索取,在网上狂览一通。心里感激的同时也想奉献一些,可是我会什么?后来使用avr(公司要求 ...… 查看全部问答> |
|
本文讨论的四种常用FPGA/CPLD设计思想与技巧:乒乓操作、串并转换、流水线操作、数据接口同步化,都是FPGA/CPLD逻辑设计的内在规律的体现,合理地采用这些设计思想能在FPGA/CPLD设计工作种取得事半功倍的效果。 FPGA/CPLD的设计思想与技巧是一个非 ...… 查看全部问答> |
|
本帖最后由 dontium 于 2015-1-23 11:37 编辑 如何最大限度减少线缆设计中的串扰 deyisupport./blog/b/analogwire/archive/2014/01/22/51614.aspx 解决串扰的最好办法就是屏蔽它,可是参考点怎么选,最后还是会有电容存在的,尽可能减小的话就只 ...… 查看全部问答> |




