Chinaunix首页 | 论坛 | 博客
  • 博客访问: 831573
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: 嵌入式

2015-11-06 10:49:34

设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口
设备控制接口(ioctl 函数):

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

-->   static int second_driver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

这是驱动程序设备控制接口函数(ioctl函数)的内核原型定义,struct inode *struct file* 描述了操作的文件,unsigned int 描述了ioctl命令号,这是一个重要的参数,我们稍后会对它做详细介绍。最后一个参数是unsigned long数据类型,描述了ioctl命令可能带有的参数,它可能是一个整数或指针数据。

  • ioctl命令号
ioctl命令号是这个函数中最重要的参数,它描述的ioctl要处理的命令。Linux中使用一个32位的数据来编码ioctl命令,它包含四个部分:dir:type:nr:size。
  • dir
代表数据传输的方向,占2位,可以是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,当然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。
  • type
描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'。
  • nr
ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号。
  • size
ioctl命令的参数大小,一般14位。ioctl命令号的这个数据成员不是强制使用的,你可以不使用它,但是我们建议你指定这个数据成员,通过它我们可以检查用户空间数据的大小以避免错误的数据操作,也可以实现兼容旧版本的ioctl命令。
我们可以自己来直接指定一个ioctl命令号,它可能仅仅是一个整数集,但Linux中的ioctl命令号都是有特定含义的,因此通常我们不推荐这么做。其实Linux内核已经提供了相应的宏来自动生成ioctl命令号:

_IO(type,nr)
_IOR(type,nr,size)
_IOW(type,nr,size)
_IOWR(type,nr,size)


宏_IO用于无数据传输,宏_IOR用于从设备读数据,宏 _IOW用于向设备写数据,宏_IOWR用于同时有读写数据的IOCTL命令。相对的,Linux内核也提供了相应的宏来从ioctl命令号种解码相应的域值:

_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)


这些宏都定义在头文件中(一般 在头文件中)。一般在使用中,先指定各个IOCTL命令的顺序编号(一般从0开始),然后根据使用的环境用这 些宏来自动生成IOCTL命令号,在后面的例子中你可以了解实际的使用场景。
  • ioctl返回值
ioctl函数的返回值是一个整数类型的值,如果命令执行成功,ioctl返回零,如 果出现错误,ioctl函数应该返回一个负值。这个负值会作为errno值反馈给调用此ioctl的用户空间程序。关于返回值的具体含义,请参 考和头文件。
  • ioctl参数
这里有必要说明一下ioctl命令的参数,因为它很容易犯错误。如果ioctl命令参数仅仅是一个整数,那么事情很简单了,我们可以在ioctl函数中直接使用它。但如果它是一个指针数据,那么使用上就要小心了。首先要说明这个参数是有用户空间的程序传递过来的,因此这个指针指向的地址是用户空间地址,在Linux中,用户空间地址是一个虚拟地址,在内核空间是无法直接使用它的。为了解决 在内核空间使用用户空间地址的数据,Linux内核提供了以下函数,它们用于在内核空间访问用户空间的数据,定义 在头文件中:

unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n);
unsigned long __must_check copy_from_user(void *to,
const void __user *from, unsigned long n);


copy_from_user和copy_to_user一般用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:

#define get_user(x,ptr)
#define put_user(x,ptr)


其中,x是内核空间的简单数据类型地址,ptr是用户空间地址指针。
我们需要牢记:在内核中是无法直接访问用户空间地址数据的。因此凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。
这里有必要再一次强调的是,在内核模块或驱动程序的编写中,我们强烈建议你使用内核提供的接口来生成并操作ioctl命令号,这样可以对命令号赋予特定的含义,使我们的程序更加的健壮;另一方面也可以提高程序的可移植性。

举例
好了,是时候举个例子了。我们将扩展我们的helloworld驱动添加ioctl函数。

首先,我们添加一个头文件来定义ioctl接口需要用到的数据(hello.h):

点击(此处)折叠或打开

  1. #ifndef _HELLO_H
  2. #define _HELLO_H
  3. #include <asm/ioctl.h>
  4. #define MAXBUF 20
  5. typedef struct _buf_data{
  6. int size;
  7. char data [MAXBUF];
  8. }buf_data;

  9. #define HELLO_IOCTL_NR_BASE 0
  10. #define HELLO_IOCTL_NR_SET_DATA (HELLO_IOCTL_NR_BASE + 1)
  11. #define HELLO_IOCTL_NR_MAX (HELLO_IOCTL_NR_GET_BUFF + 1)

  12. #define HELLO_IOCTL_SET_DATA _IOR('h', HELLO_IOCTL_NR_SET_DATA, buf_data*)

  13. #endif

然后为我们的驱动程序添加ioctl接口hello_ioctl,并实现这个函数:

点击(此处)折叠或打开

  1. static int hello_ioctl (struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
  2. {
  3.     int cmd_nr;
  4.     int err;
  5.     buf_data buff;

  6.     err = 0;
  7.     cmd_nr = _IOC_NR (cmd);
  8.     
  9.     switch (cmd_nr){
  10.         case HELLO_IOCTL_NR_SET_DATA:
  11.             if (copy_from_user(&buff, (unsigned char *)arg, sizeof(buf_data)))
  12.             {
  13.                 err = -ENOMEM;
  14.                 goto error;
  15.             }
  16.             memset(hello_buf, 0, sizeof(hello_buf));
  17.             memcpy(hello_buf, buff.data, buff.size);
  18.         break;
  19.         
  20.         default:
  21.             printk("hello_ioctl: Unknown ioctl command (%d)\n", cmd);
  22.         break;
  23.     }

  24.     error:
  25.     return err;
  26. }

  27. static struct file_operations hello_fops = {
  28.     .read = hello_read,
  29.     .write = hello_write,
  30.     .open = hello_open,
  31.     .ioctl = hello_ioctl,
  32.     .release = hello_release,
  33. };






一个控制LED的例子:(arg 传递指针数据 或者直接传递参数)
测试程序:LED_Test.c

点击(此处)折叠或打开

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdio.h>

  5. int main(int argc, char * argv[])
  6. {
  7.     int fd;
  8.     int cmd, led_num;
  9.     
  10.     fd = open("/dev/Button", O_RDWR);
  11.     if(fd < 0)
  12.         printf("can't open file \n");

  13.     if(argc < 3)
  14.     {
  15.         printf("Usage: \n");
  16.         printf("%s on|off 0|1|2\n", argv[0]);
  17.         return 0;
  18.     }

  19.     if(strcmp(argv[1], "on") == 0)
  20.     {
  21.         cmd = 1;
  22.     }
  23.     else
  24.         cmd = 0;

  25.     if(strcmp(argv[2], "0") == 0)
  26.         led_num = 0;
  27.     else if(strcmp(argv[2], "1") == 0)
  28.         led_num = 1;
  29.     else if (strcmp(argv[2], "2") == 0)
  30.         led_num = 2;
  31.     else if(strcmp(argv[2], "all") == 0)
  32.         led_num = 3;


  33.     // 1. 利用指针传递数据。
  34.     ioctl(fd, cmd, &led_num)

  35.     // 2. 可以改为参数传递数据
  36.     ioctl(fd, cmd, led_num);
  37.     
  38.   
  39.     return 0;
  40. }

驱动函数:LED_Driver.c

点击(此处)折叠或打开

  1. static int second_driver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
  2. {
  3.     // 1. 利用指针传递参数的做法
  4.     unsigned int *ptr = (unsigned int *)arg;   // 把指针里的数据读出来
  5.     unsigned int led_num = *ptr;
  6.    
  7.     // 2. 直接传递参数的做法
  8.     unsigned int led_num = (unsigned int)arg;   

  9.     printk("arg: %d \n", led_num);
  10.     switch(cmd)
  11.     {
  12.         case LED_ON:
  13.         
  14.             if(led_num == 3)
  15.                 ALL_LED_ON();
  16.             else
  17.                 LED_ON_NUM(led_num);
  18.                 
  19.         break;

  20.         case LED_OFF:
  21.             if(led_num == 3)
  22.                 ALL_LED_OFF();
  23.             else
  24.                 LED_OFF_NUM(led_num);
  25.         break;

  26.         default:
  27.         break;
  28.     }
  29.     
  30.     
  31.     return 0;
  32. }









阅读(565) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~