前边写了一个驱动模型的小模块,但是很简单,module_init里边连注册函数都没有,只是有一printk函数,能表示出insmod时的函数关系。突然要把pwmo模块写成真正的驱动,并且按驱动进行操作,谁知还小磨了一番。本文是在网上察看和看书后根据自己理解在前人的基础上进行总结。 一、关于应用程序里的open()函数: 1、open()函数原型为: int open( const char * pathname, int flags); int open( const char * pathname,int flags, mode_t mode); 它有两个原型,系统会自动识别参数并进行正确调用。const char * pathname为要打开的节点的路径,比如/dev/lcd_bklight。int flags为打开的方式,有以下标示: O_RDONLY 以只读方式打开文件 O_WRONLY 以只写方式打开文件 O_RDWR 以读写方式打开文件。 这三个选项互斥。 2、open()使用: int fd; fd= open(“dev/lcd_bklight” ,O_RDWR); 打开不成功,则返回-1,若打开成功则返回文件描述符,它实际上是内核所创建的一个相应结构体数组的下标,而结构体记录了用户操作文件所需的数据和函数入口。 (这个结构体不是chardevs[],它是在注册时创建的,并以主设备号作为下标。感觉像是struct file结构体,他是内核在文件打开时创建的文件描述结构体,并传给所有的文件操作函数。而该结构体中确实含有该设备操作的file_operations的指针,以及其他的数据指针。) 文件打开后,对文件的操作只需只需按标准的系统函数,比如 i=write(fd,&num,sizeof(int)); 或 i=read(fd,&num,sizeof(int)); 二、一个简单的调用驱动函数 在调驱动的时候,刚开始出了问题,而对驱动和应用调用都不熟,也不知道是哪的问题,在网上找了一个调用驱动的简单应用例子,简洁明了,让人一下子搞懂了调用驱动的过程,从而排除了一部分错误,最后也把驱动的错误找了出来。 example.c的代码为: #include #include #include #include #include main() { int fd,size; char s[]="Linux Programmer!\n",buffer[80]; fd=open("/tmp/temp",O_WRONLY|O_CREAT); printf("open 1 fd=%d \n",fd); write(fd,s,sizeof(s)); close(fd); fd=open("/tmp/temp",O_RDONLY); printf("open 2 fd=%d \n",fd); size=read(fd,buffer,sizeof(buffer)); close(fd); printf("%s\n",buffer); } Gcc后一试,很成功,没有错误。知道了系统调用只不过是先open一个设备,而后调用系统函数read或write进行读写。 三、一个通用的驱动makefile 嵌入式的驱动最后是要操作硬件板子的,但是之前的模块调试(不涉及硬件外设时,只是模块间的调用关系和内存操作)却可以在x86上来完成,而每次都把代码加到内核后用make modules来完成显得很麻烦,这里找到一个makefile,只需修改一下生成的文件名就可以使用,很是方便,如下: obj-m :=lcd_light.o KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: 只需把makefile和源文件放到一块,修改lcd_light.o为(源文件名 .o )即可。 四、驱动代码 这里所写的驱动一个lcd的背光调整的的驱动,它是操作一个mx27芯片的pwmo模块,以输出不同占空比的pwm波。 因为实在x86上进行调试,所以把具体操作板子外设的代码给屏蔽掉了。 代码lcd_light.c如下: #include #include #include #include #include #include #include #include MODULE_LICENSE("Dual BSD/GPL"); typedef unsigned int UINT32; static UINT32 lcd_light_degree; int major_lcd_light; static ssize_t lcd_light_write(struct file *file, const char *buf, size_t count, loff_t *off) { //copy_from_user(&lcd_light_degree, (void*)buf, sizeof(int)); copy_from_user(&lcd_light_degree, (void*)buf, count); //__raw_writel(lcd_light_degree ,AIPI_IO_ADDRESS(0x1000600C));//sample printk("LCD light degree is %d", lcd_light_degree); return count; } static ssize_t lcd_light_read(struct file *file, char *buf, size_t bytes, loff_t *off){ //__raw_readl(lcd_light_degree ,AIPI_IO_ADDRESS(0x1000600C));//sample copy_to_user((void*)buf, &lcd_light_degree,count); return bytes; } static ssize_t lcd_light_open(struct inode *inode, struct file *file){ printk("device open:%d,%d\n",inode->i_rdev>>8,inode->i_rdev&0xFF); return 0; } static ssize_t lcd_light_release(struct inode *inode, struct file *file){ printk("device release:%d,%d\n",inode->i_rdev>>8,inode->i_rdev&0xFF); return 0; } static struct file_operations lcd_light_fops = { .owner = THIS_MODULE, .write = lcd_light_write, .read = lcd_light_read, .open = lcd_light_open, .release = lcd_light_release, }; static int LCD_light_init(void) { lcd_light_degree = 10; major_lcd_light = register_chrdev(0, "lcd_light", &lcd_light_fops); if (major_lcd_light { printk(KERN_INFO "Unable to get a major for lcd light"); return major_lcd_light; } else { //__raw_writel(__raw_readl(AIPI_IO_ADDRESS(0x10015400))|0x00000020,AIPI_IO_ADDRESS(0x10015400));//direction //__raw_writel(__raw_readl(AIPI_IO_ADDRESS(0x10015420))&0xffffffdf,AIPI_IO_ADDRESS(0x10015420)); gpio1 OR multiplex0 //__raw_writel(__raw_readl(AIPI_IO_ADDRESS(0x10015438))&0xffffffdf,AIPI_IO_ADDRESS(0x10015438));//0,primary;1,alternate //__raw_writel(0x00030000,AIPI_IO_ADDRESS(0x10006000));//init //__raw_writel(0x0000000D,AIPI_IO_ADDRESS(0x10006010));//period //__raw_writel(lcd_light_degree ,AIPI_IO_ADDRESS(0x1000600C));//sample //__raw_writel(0x00030001,AIPI_IO_ADDRESS(0x10006000));//en printk(KERN_ALERT " lcd light major is %d.\n", major_lcd_light); return 0; } } static void LCD_light_exit(void) { unregister_chrdev(major_lcd_light, "lcd_light"); printk(KERN_ALERT "LCD light exit.\n"); } module_init(LCD_light_init); module_exit(LCD_light_exit); MODULE_AUTHOR("beny"); MODULE_DESCRIPTION("LCD backlight driver"); MODULE_ALIAS("lcd back light driver module"); 这个函数很简单,但曾经犯了一个错误,就是把驱动中的open的函数原形搞错了,导致在打开设备的时候总报Segmentation fault,可能是不能正确的打开设备的原因吧。还有一个不是void型的函数,如果没有return是总包一个warning: control reaches end of non-void function。分析其中一个函数: static ssize_t lcd_light_write(struct file *file, const char *buf, size_t count, loff_t *off) { copy_from_user(&lcd_light_degree, (void*)buf, count); printk("LCD light degree is %d", lcd_light_degree); return count; } 函数只需按照模型参数写就可以了,其中*buf为用户空间的数据指针,count为要写进去的数据量。当应用程打开设备后进行写时,只需传递三个参数,比如: i=write(fd,&num,sizeof(int)); 系统调用会自动找到lcd_light_write()这个调用函数,并把参数正确传递,然后执行代码。上边这个write的fd若是lcd_light,则会把num的数据写道内和空间的lcd_light_degree中。 五、测试代码: 测试代码分为两个部分,一个是写入数据,一个是读出数据,看读出的是不是写入的数据,以证明数据读写是否正确。 1、write.c的代码如下: #include #include //O_RDWR defined #include //S_IRUSR , S_IWUSR defined #include //errno EACCES defined #include #include main() { int fd,num,i=0; //fd=open("/dev/lcd_light",O_RDWR,S_IRUSR | S_IWUSR); fd=open("/dev/lcd_light",O_RDWR); if(fd!=-1) ////open successed!!!! { if(errno == EACCES) printf("No permission\n"); else if(errno == ENOENT) printf("File doesnt exist\n"); else printf("File opened!\n"); printf("the open return is %d.\n",fd); printf("please enter the number you want write to.\n"); scanf("%d",&num); i=write(fd,&num,sizeof(int)); if(errno == EBADF) printf("errno==EBADF,Bad file descriptor!!\n"); printf("the return of write is %d .\n",i); close(fd); } else {printf("device open failed!!");} } 2、read.c的代码如下: #include #include //S_IRUSR , S_IWUSR defined #include //O_RDWR defined int main(void) { int fd, num,i; fd = open("/dev/lcd_light",O_RDWR); if(fd != -1) { i=read(fd,&num,sizeof(int)); printf("read return is %d\n",i); printf("read value is %d\n",num); close(fd); } else { printf("open my_driver failure\n"); } return 0; } 测试代码直接用gcc编译就可以了,编译后就可以直接运行。 六、x86上测试 首先要编译lcd_lignt.c这个驱动文件,只需进入文件夹后,编写如上的makefile,然后执行make即可。 然后: 1、 insmod lcd_lignt.o 2、 cat /proc/devices 可以看到lcd_lignt的设备好为253,(我的是,动态的,不确定) 3、 mknod /dev/lcd_lignt c 253 0 设备建立成功,分别gcc read.c –o read.o gcc write.c –o write.o后运行: 4、运行./write.o root@tryflying:/home/read# ./write.o File opened! the open return is 3. please enter the number you want write to. 8 the return of write is 4 . 5、运行./read.o root@tryflying:/home/read# ./read.o read return is 4 read value is 8 从运行的结果可以看到,写入的数据8被正确的读了出来,说明驱动完全正确。 七、一点对驱动及其调用的感受 开始对驱动及其调用没有概念,搞起来好晕,现在知道了个大概: 驱动只需按照模版写就可以了,里边的参数不要动,驱动注册后,系统会添加chardev结构体数组,索引为设备号;然后就是建立节点,刚好包含了注册的信息;当打开这个设备的节点文件时,内核会建立对应的struct file,返回该设备的文件描述符,也就是struct file结构体数组的下标;而这个结构体包含了设备文件的操作函数指针(file_operations指针)以及其他的设备资源等,在read、write等应用程序中传递了文件描述符fd,也就是传递了驱动的函数指针,系统会自动转化为对应的驱动调用,也就是file_operations里边的read、write(例子里边分别为lcd_light_read、lcd_light_write),传递参数并运行。 以上理解可能有误,还请大家多多指教!! |
|小黑屋|最新主题|手机版|微赢网络技术论坛 ( 苏ICP备08020429号 )
GMT+8, 2024-9-30 07:21 , Processed in 0.220619 second(s), 12 queries , Gzip On, MemCache On.
Powered by Discuz! X3.5
© 2001-2023 Discuz! Team.