找回密码
 注册
搜索
热搜: 回贴
微赢网络技术论坛 门户 服务器 Linux/BSD 查看内容

驱动模块及其用户空间的调用

2009-12-20 13:27| 发布者: admin| 查看: 39| 评论: 0|原作者: 千年缘

前边写了一个驱动模型的小模块,但是很简单,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),传递参数并运行。

以上理解可能有误,还请大家多多指教!!







最新评论

QQ|小黑屋|最新主题|手机版|微赢网络技术论坛 ( 苏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.

返回顶部