字符设备驱动(二)
来源:互联网 发布:统计年鉴数据怎么下载 编辑:程序博客网 时间:2024/06/08 06:38
驱动LED灯
首先加入头文件
#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>
还要定义几个类
static struct class *leds_class;static struct class_device *leds_class_devs[4];
定义一个类,然后再在类下定义4个设备,下面会用到。
然后创建open函数myled_open
static int myled_open(struct inode *inode, struct file *file){ *gpfcon &= ~((0x3<<4*2)|(0x03<<5*2)|(0x3<<6*2)); *gpfcon |= (1<<4*2)|(1<<5*2)|(1<<6*2); return 0;}
当再测试文件中执行open(“/dev/xxx”,”x”);时,系统就会根据file_operations结构体运行myled_open函数。所以在这个函数中初始化led引脚为输出。该函数参数为 设备节点结构体指针,和文件流指针。
然后创建write函数myled_write
static int myled_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos){ int minor=MINOR(file->f_dentry->d_inode->i_rdev); char val; copy_from_user(&val,buf,count);//copy variable from user to kernel switch(minor){ case 0: { if(val==1)*gpfdat &=(0<<4|0<<5|0<<6);//点灯 else *gpfdat |=(1<<4|1<<5|1<<6);//关灯 break; } case 1: { *gpfdat |=(1<<4|1<<5|1<<6);//关灯 if(val==1) *gpfdat &=(~(1<<4));//点灯 break; } case 2: { *gpfdat |=(1<<4|1<<5|1<<6);//关灯 if(val==1) *gpfdat &=(~(1<<5)); break; } case 3: { *gpfdat |=(1<<4|1<<5|1<<6);//关灯 if(val==1) *gpfdat &=(~(1<<6)); break; } } return 0;}
myled_write函数的参数为文件流指针、传递进来的数据指针、数据大小
MINOR(file->f_dentry->d_inode->i_rdev);是取文件的次设备号,copy_from_user(void *to, const void __user *from, unsigned long n)从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.
当测试程序执行write(fd,&val,1);时,系统就会调用该函数。fd为执行open函数时返回的唯一的文件文件描述符,
然后接下来就是填充file_operations结构体
static struct .file_operations myled_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = myled_open, .write = myled_write, };
系统启动后,会自动执行这个结构体,然后将创建的函数与底层open,write函数关联起来。
接下来就是注册函数
static int myled_init(void)//加载{ major=register_chrdev(111,"myled",&myled_fops);//major为自己定义的全局整型变量 if (major < 0) { printk("myled can't register major number\n"); return major; } myled_class=class_create(THIS_MODULE, "myled"); if (IS_ERR(myled_class)) return PTR_ERR(myled_class); int i; for(i=0;i<4;i++){ myled_class_devs[i]=class_device_create(myled_class, NULL, MKDEV(111, i), NULL, "led%d",i);// if (unlikely(IS_ERR(myled_class_devs[i])))//先建立一个类 再建立一个设备,然后自动创建一个xyz设备节点 return PTR_ERR(myled_class_devs[i]); } gpfcon=(unsigned long *)ioremap(0x56000050,16); gpfdat=gpfcon+1; return 0;}
major=register_chrdev(111,”myled”,&myled_fops);就是将file_operations创建的myled_fops结构题告诉内核,然后给该设备的主设备号赋值为111,设备名字为myled.
myled_class=class_create(THIS_MODULE, “myled”);
myled_class_devs[i]=class_device_create(myled_class, NULL, MKDEV(111, i), NULL, “led%d”,i);
就是给该设备创建一个类,然后再创建的类下,再创建四个设备节点。最终在系统中节点是/dev/led0 /dev/led1 /dev/led2 /dev/led3.应用层序中open函数中的参数就是这些节点。它们的主设备号相同,但是次设备号不同。都归属于myled这个节点。
gpfcon=(unsigned long *)ioremap(0x56000050,16);
gpfdat=gpfcon+1;
ioremap函数就是将一段连续的物理地址映射为虚拟地址。因为指针大小为4字节,所以加一就表示加了4字节。在注册函数中映射地址,那么就得在卸载函数中取消映射地址。
下面是卸载函数
static void myled_exit(void){ int i; unregister_chrdev(111,"myled");//卸载; for(i=0;i<4;i++){ class_device_unregister(myled_class_devs[i]); } class_destroy(myled_class); iounmap(gpfcon);}
unregister_chrdev(111,”myled”);是从内核中删除创建的myled_fops结构体,这里只需要主设备号和设备名。
class_device_unregister(myled_class_devs[i]);class_destroy(myled_class);与上面的创建类的函数相对应
iounmap(gpfcon);就是取消虚拟地址映射。
最后还要加上如下几行
module_init(myled_init);module_exit(myled_exit);MODULE_LICENSE("GPL");
module_init(myled_init);创建一个结构体,里面有myled_init函数的地址,当执行insmod myled.ko时,系统会自动找到这个结构体,然后找到里面的函数地址,去执行myled_init。
module_exit(myled_exit);创建一个结构体,里面有first_drv_init函数的地址,当执行rmmod myled.ko时,系统会自动找到这个结构体,然后找到里面的函数地址,去执行myled_exit
MODULE_LICENSE(“GPL”);是将钥匙设置为GPL.
下面是测试函数
加入头文件
#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<stdio.h>
void print_usage(char *file){ printf("Usage:\n"); printf("%s /dev/led0 <on|off>\n", file); printf("%s /dev/led1 <on|off>\n", file); printf("%s /dev/led2 <on|off>\n", file); printf("%s /dev/led3 <on|off>\n", file);}int main(int argc,char *argv[]){ int fd; int val=1; if(argc<3){ print_usage(argv[0]); return 0; } fd=open(argv[1],O_RDWR); if(fd<0){ printf("error,can't open %s\n",argv[1]); return 0; } if(strcmp(argv[2],"on")==0) val=1; else if(strcmp(argv[2],"off")==0) val=0; else print_usage(argv[0]); write(fd,&val,1); return 0;}
例如执行/myledtest /dev/led0 on
当执行 fd=open(argv[1],O_RDWR);函数时,就会运行/dev/led0对应open函数,返回的是每个次设备号不同的描述符,每个主设备里的次设备的描述符不一样。
然后下面执行write(fd,&val,1);就会运行/dev/led0对应的write函数。
执行/myledtest /dev/led1 on /myledtest /dev/led2 on时也会这样。
是因为在注册函数中将同一主设备号的write,read函数填充在myled_fops结构体中了。然后又将这个结构体和主设备名绑定在一起并告诉内核了,然后又利用主设备名创建一个类,类里又创建四个次设备。所以每次调用次设备时就会执行主设备对应的底层函数,然后在该函数中分别是哪一个次设备调用的。如int minor=MINOR(file->f_dentry->d_inode->i_rdev);就是返回调用该函数的次设备号。
- 字符设备驱动(二)
- 字符设备驱动(二)
- 字符设备驱动(二)
- Linux设备驱动(二)字符设备
- 字符设备驱动(二)
- linux设备驱动(二)---字符设备之按键驱动
- Linux字符设备驱动(二)
- 字符设备驱动有关(二)
- linux字符设备驱动框架(二)
- 字符设备驱动笔记(二)
- 设备驱动之二----字符设备驱动
- 字符设备驱动学习(二) 设备接点的创建
- Linux设备驱动之字符设备(二)
- Linux字符设备驱动(二)
- Linux字符设备驱动(二)
- Linux字符设备驱动(二)
- Linux字符设备驱动(二)
- 【嵌入式Linux驱动开发】三、字符设备驱动(二)
- linux系统上eclipse的hadoop开发环境准备
- 整数划分(四)
- UIViewController
- Codeforces Round #145 (Div. 1, ACM-ICPC Rules)B dp
- Redis学习笔记(二)---String类型和hash类型
- 字符设备驱动(二)
- Java web 打印方案
- YTUOJ-比较大小-类模板
- 顺序栈
- android 传感器 Sensor
- 黑马程序员-JAVA-多线程使用初探
- java反射类成员变量
- Android性能优化Google课程翻译一:Render
- 2015、4、21记录