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

前边写了一个驱动模型的小模块,但是很简单,module_init里边连注册函数都没有,只是有一printk函数,能表示出insmod时的函数关系。突然要把pwmo模块写成真正的驱动,并且按驱动进行操作,谁知还小磨了一番。本文是在网上察看和看书后根据自己理解在前人的基础上进行总结。

 

一、关于应用程序里的open()函数:

1open()函数原型为:

int open( const char * pathname, int flags);
int open( const char * pathname,int flags, mode_t mode);

它有两个原型,系统会自动识别参数并进行正确调用。const char * pathname为要打开的节点的路径,比如/dev/lcd_bklightint flags为打开的方式,有以下标示:

O_RDONLY 以只读方式打开文件
O_WRONLY
以只写方式打开文件
O_RDWR   
以读写方式打开文件。    这三个选项互斥。

2open()使用:

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 <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

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一个设备,而后调用系统函数readwrite进行读写。

三、一个通用的驱动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 <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/device.h>

#include <linux/slab.h>

#include <linux/fs.h>

#include <asm/semaphore.h>

#include <asm/uaccess.h>

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(0x 1000600C ));//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(0x 1000600C ));//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("<1>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("<1>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 < 0)

    {

    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(0x 1000600C ));//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()这个调用函数,并把参数正确传递,然后执行代码。上边这个writefd若是lcd_light,则会把num的数据写道内和空间的lcd_light_degree中。

五、测试代码:

测试代码分为两个部分,一个是写入数据,一个是读出数据,看读出的是不是写入的数据,以证明数据读写是否正确。

1write.c的代码如下:

#include <stdio.h>

#include <fcntl.h>         //O_RDWR defined

#include <sys/stat.h>       //S_IRUSR , S_IWUSR  defined

#include <errno.h>         //errno EACCES defined

#include <sys/types.h>

#include <unistd.h>

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!!");}

}

 

 

2read.c的代码如下:

#include <stdio.h>

#include <sys/stat.h>  //S_IRUSR , S_IWUSR  defined

#include <fcntl.h>    //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指针)以及其他的设备资源等,在readwrite等应用程序中传递了文件描述符fd,也就是传递了驱动的函数指针,系统会自动转化为对应的驱动调用,也就是file_operations里边的readwrite(例子里边分别为lcd_light_readlcd_light_write),传递参数并运行。

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值