嵌入式Linux 字符设备驱动标准ioctl接口

简介: 嵌入式Linux 字符设备驱动标准ioctl接口

1、unlocked _ioctl接口作用

write:往设备写中写数据,单独这个接口并不能满足现实设备的全部控制需求。

一个lcd控制器:主要作用就是驱动lcd屏,要显示就是通过write接口把显示数据发给lcd控制器指定的显存。而参数设置类通过write接口设置就可能会和普通的显示数据弄混乱了。为了解决这个问题,内核提供了ioctl接口专门对设备控制(参数设置,参数查询等功能)。

ioctl主要用于实现对硬件设备控制类操作,使用write和read不太好实现的功能


2、ioctl系统调用

#include <sys/ioctl.h>

原型:int  ioctl(int fd, int request, ...);

功能:通过命令形式来控制硬件设备,相当linux系统给我们提供扩展系统功能的一个接口,可以由用户自定义命令来让硬件执行不同的代码。

参数:

fd: 文件描述符

request: 发给设备的命令,这个命令可以是系统预定义的,也可是用户自定义

…:表示变参,可选择的,相当于printf参数一样,可以有多个,也可以没有。是否需要传送和request参数有关。

示例:有4个命令:1)0x10表示开全部灯;  2)0x20 表示开第N个灯;3)0x30表示关第N个灯;4)0x40表示关全部灯;

使用上面的ioctl函数来控制:

1)表示开全部灯;  

ioctl(fd,0x10)     //全部开

2)0x20 表示开第N个灯;

ioctl(fd,0x20,2)   //开第二灯

3)0x30表示关第N个灯;

ioctl(fd,0x20,1)   //关第一灯

4)0x40表示关全部灯;

ioctl(fd,0x40)     //全部关

返回:

>=0 : 执行成功,>0时候值是什么含义由驱动程序中决定。

-1  : 执行失败


3、unlocked_ioctl接口驱动模板

在文件操作方法结构体当中定义如下:

struct file_operations {

   ……

   long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

   ……

}

原型:long  xxx_unlocked_ioctl(struct file *pfile, unsigned int cmd, unsigned long args);

功能:对应于应用程序编程API(系统调用)接口的ioctl函数。

参数:pfile:文件结构指针,间接对应于系统调用的fd参数(文件描述符)

cmd:对应于系统调用的request ; args:args系统调用的…参数(可选参数)

返回值:>=0 : cmd命令执行成功,>0时候值是什么含义由驱动程序中决定。

< 0 :  cmd命令执行失败,返回失败错误码,比如args参数非法,则返回-EFAULT,所返回的错误码会被系统存储在全局变量errno中,应用程序可以这个变量。

错误码:EFAULT  args非法;EINVAL  参数无效。


4、unlocked_ioctl接口测试体验

4.1 unlocked_ioctl接口

设计思路:由于应用程序的ioctl函数中request参数对应于驱动的cmd参数,而应用程序通过传递不同的值来告诉驱动程序做不同事情,所以,驱动中的unlocked_ioctl接口代码内部一定是要判断cmd值来决定执行不同的代码,所以就是一个if语句或switch语句完成,对cmd值的判断。

#include <linux/module.h>      
#include <linux/init.h>          
#include <linux/fs.h>           
#include <asm/io.h>           
#include <asm/uaccess.h>      
#include <linux/miscdevice.h>
#define   DEVICE_NAME      "ioctl-demo"
static long chrdev_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long arg)
{
  switch ( cmd ) {
    case 0 :
      printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
      break;
    case 1 :
      printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
      break;
    case 2 :
      printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
      break;
    case 3 :
      printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
      break;
    case 4 :
      printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
      break;
    default:
      printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg);
      return -EINVAL;
  }
  return arg;
}
//文件操作方法:
static const  struct file_operations xxx_fops = {
  .unlocked_ioctl = chrdev_unlocked_ioctl,
};
static struct miscdevice  xxx_dev =  {
  .fops   =  &xxx_fops,            
  .minor  = 255,                  
  .name   =  DEVICE_NAME,   
};
//模块初始化函数
static int __init xxx_dev_init( void )
{
  int ret;
  ret = misc_register (&xxx_dev );
  if(ret<0){
    printk ("module install fail !\n" );
    return ret;
  }   
  printk ("module install success !\n" );
  return 0;
}
//模块卸载函数
static void  __exit xxx_dev_exit ( void )
{
  misc_deregister ( &xxx_dev);
  printk ("module uninstall success !\n" );
}
module_init (xxx_dev_init );
module_exit (xxx_dev_exit );
MODULE_LICENSE ( "GPL" );
MODULE_AUTHOR  ( "Chenzhifa" );
MODULE_DESCRIPTION ( "STUDY_MODULE" );

测试结果:

[  911.013119] module install success !

[root@edu118 home]# ./app

[  913.403718] line:14,cmd:0,arg:10

ret = 10

line:17,cmd:1,arg:11

ret = 11

ret = -1

line:23,cmd:3,arg:13

ret = 13

line:26,cmd:4,arg:14

ret = 14

line:29,cmd:5,arg:15

ret = -1

[root@edu118 home]#

 

 

上面的测试得到出几点结论:

1. 知道用户空间中应用程序ioctl函数和驱动程序 .unlocked_ioctl 接口的参数对应关系。

2. 某一些cmd在自己的驱动中不能使用。

3. 可以使用这接口来实现对Led灯控制。--- 很容易实现。

5、标准unlocked_ioctl接口的命令合成

5.1 接口命令规则

1. 系统自定义命令:执行优先级会高于用户自定义命令

2. 用户自定义命令

ioctl执行不直接就执行驱动程序中.unlocked_ioctl接口,而是先根据cmd情况判断是否是属于预定义命令,如果是则去执行完成后返回了。返回后可能是执行用户自定义命令,也可能直接返回,不执行用户命令。

避免命令冲突:内核为解决这个问题,定义一个规则,命令值是特定格式组成 的。

Ioctl-decoding.txt linux-3.5\documentation\Ioctl  

解释了cmd值的组成格式:

bits      meaning

31-30   00 – 用户程序和驱动没有数据传递 uses  _IO   macro

10 – 用户程序从驱动读取数据: _IOR

01 – 用户程序向驱动写数据: _IOW

11 -  先用户程序写数据到驱动,再从驱动中读取数据(先写数据然后读取数据回来): _IOWR

29-16   当用户程序和驱动程序有数据传递时候才有效,表示要传递的数据大小。

示例:A,B两个驱动的CMD值它们的0~7可以相同,但是8~15最好不相同。A驱动中的所有CMD命令值的8~15位都应该是相同,B驱动所有CMD值8~15位值也是相同

led cmd示例:

#define   IOC_CMD_LED_ON_ALL    0<<30 | 0<<16 | 'L' | 0      //全部开
#define   IOC_CMD_LED_OFF_ALL   1<<30 | 4<<16 | 'L' | 1      //指定灯开
#define   IOC_CMD_ON_X          1<<30 | 4<<16 | 'L' | 2      //指定灯关
#define   IOC_CMD_OFF_X         0<<30 | 0<<16 | 'L' | 3      //全部关

 

这样是不会冲突的,内核提供了相应的命令合成和分解宏,定义在asm-generic\ioctl.h  文件中,如下:

_IO(type,nr)  :定义没有数据传递的命令

_IOR(type,nr,size)  :定义从驱动中读取数据的命令

_IOW(type,nr,size)  :定义向驱动写入数据的命令

_IOWR(type,nr,size) :定义数据交换类型的命令,先写入数据,再读取数据这类命令。

type:表示命令组成的魔数,也就是8~15位;

nr:表示命令组成的编号,也就是0~7位

size:表示命令组成的参数传递大小,但是这里不传递数字,而是数据类型,如要传递4字节,可以写int,如要传递一个结构体数据给驱动,则把结构类型做size参数。

使用内核定义宏来改造前面定义的4条命令:

//定义命令 
#define IOC_CMD_LED_MAGIC       'L' 
#define IOC_CMD_LED_ON_ALL      _IO(IOC_CMD_LED_MAGIC,0)             //全部开
#define IOC_CMD_LED_OFF_ALL     _IO(IOC_CMD_LED_MAGIC,1)             //全部关
#define IOC_CMD_ON_X            _IOW(IOC_CMD_LED_MAGIC,2,int)        //指定灯开
#define IOC_CMD_OFF_X           _IOW(IOC_CMD_LED_MAGIC,3,int)        //指定灯关
#define IOC_CMD_LED_MIN_NR      0                                   //最小命令序号
#define IOC_CMD_LED_MAX_NR      3                                   //最大命令序号

内核也提供也分解命令各部分的宏:

/* used to decode ioctl numbers.. */

_IOC_DIR(nr)    分解出命令的方向,也就是上面说30~31位的值

_IOC_TYPE(nr)    分解出命令的魔数,也就是上面说8~15位的值

_IOC_NR(nr)    分解出命令的编号,也就是上面说0~8位

_IOC_SIZE(nr)    分解出命令的复制数据大小,也就是上面说的16~29位

nr : 要分解的cmd值

示例:

_IOC_TYPE(LED_ON_X)   结果是  'L'  

_IOC_NR(LED_ON_X)   结果是  1  

Ioctl-number.txt linux-3.5\documentation\Ioctl

这个文件中列出当前内核哪些魔数和对应命令编号已经使用了,你不应该再去使用这些命令,否则就可能冲突。这个表只供参数,不表示一定是准确的。因为它是针对X86内核,并且是统计到2.6.31版本。但是,只要你按规则定义命令,冲突的可能性很小。

 

一个魔数如果已经使用了,你也想使用,则要避开上面描述命令号。例如:上面的魔数为0,对应的命令编号0~1F已经被使用,但是可以使用魔数为0,命令编号0x20开始的组合值。

  1. 标准unlocked_ioctl接口代码实现
  1. unlocked_ioctl接口控制led

在原来的LED驱动代码基础上修改:

1、定义命令:

单独定义在一个头文件中:ioctl_cmd.h

#ifndef __IOCTL_CMD__
#define __IOCTL_CMD__
//定义命令 
#define IOC_CMD_LED_MAGIC       'L' 
#define IOC_CMD_LED_ON_ALL      _IO(IOC_CMD_LED_MAGIC,0)             //全部开
#define IOC_CMD_LED_OFF_ALL         _IO(IOC_CMD_LED_MAGIC,1)             //全部关
#define IOC_CMD_ON_X            _IOW(IOC_CMD_LED_MAGIC,2,int)        //指定灯开
#define IOC_CMD_OFF_X           _IOW(IOC_CMD_LED_MAGIC,3,int)        //指定灯关
#define IOC_CMD_LED_MIN_NR      0                        //最小命令序号
#define IOC_CMD_LED_MAX_NR      3                        //最大命令序号
#endif

2、驱动中包含自定义命令头文件

#include”ioctl_cmd.h“

3、修改unlocked_ioctl接口

//unlocked_ioctl 接口
static long xxx_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
  int nr = 0,ret;
  //printk("1 --- GPIO_SWPORTA_DR:0x%x\r\n", readl(GPIO_SWPORTA_DR));
  if(_IOC_TYPE(cmd) != IOC_CMD_LED_MAGIC)
    return -EINVAL;
  if((_IOC_NR(cmd) < IOC_CMD_LED_MIN_NR) ||  (_IOC_NR(cmd) <IOC_CMD_LED_MIN_NR))
    return -EINVAL;   
  if(_IOC_DIR(cmd) == _IOC_WRITE){
    //复制用户空间传递下来的灯编号
    ret = copy_from_user(&nr, (void*)args, _IOC_SIZE(cmd));
    if(ret) {
      return -EFAULT;
    }
    //检测用户空间传递下来的led编号是否合法
    if(nr >= LED_NUM) {
      return -EINVAL;
    }   
  }
  switch(cmd) {
case IOC_CMD_ON_X:
    case IOC_CMD_OFF_X:
      //控制 led 亮灭
      if(cmd == IOC_CMD_ON_X) {
        xyd_rk3399_pwrled_on_off_ctrl( 1);
      } else {
        xyd_rk3399_pwrled_on_off_ctrl( 0);
      }
      break;
      //开发板只有一个灯,所以代码和上面的相同,后面硬件扩展了再修改代码
    case IOC_CMD_LED_ON_ALL: 
    case IOC_CMD_LED_OFF_ALL:
      //控制 led 亮灭
      if(cmd == IOC_CMD_LED_ON_ALL) {
        xyd_rk3399_pwrled_on_off_ctrl( 1);
      } else {
        xyd_rk3399_pwrled_on_off_ctrl( 0);
      }
    default:
      printk("ERROR:Illegal parameters\r\n");
      return -EINVAL;
  }
  //printk("2 --- GPIO_SWPORTA_DR:0x%x\r\n", readl(GPIO_SWPORTA_DR));
  return 0;
}

5.2 ioctl系统调用控制led

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>    //lseek
#include <sys/ioctl.h> //ioctl
#include "ioctl_cmd.h"
#define   DEFAULT_DEV_LED           "/dev/xyd-leds"   //默认打开 的设备名
int main(int argc, char**argv)
{
  char *dev;
  int ret, i, nr = 0, fd;
  if(argc == 1) {
    dev = DEFAULT_DEV_LED;
} else if(argc == 2) {
    dev = argv[1];
  } else {
    printf("Usage:%s  [/dev/devname]\r\n", argv[0]);
    return 0;
  }
  fd = open(dev, O_RDWR);
  if(fd < 0) {
    perror("open");
    return -1;
  }
  while(1) {
    ioctl(fd, IOC_CMD_ON_X, &nr);   //亮
    sleep(1);
    ioctl(fd, IOC_CMD_OFF_X, &nr);  //灭
    sleep(1);
  }
  //关闭文件
  close(fd);
  return 0;
}


相关文章
|
2天前
|
Linux Android开发
测试程序之提供ioctl函数应用操作GPIO适用于Linux/Android
测试程序之提供ioctl函数应用操作GPIO适用于Linux/Android
14 0
|
2天前
|
Linux 芯片 Ubuntu
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
|
2天前
|
Ubuntu Linux
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-2
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-2
|
2天前
|
Linux 芯片
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-1
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-1
|
2天前
|
Linux C语言 Ubuntu
Linux驱动入门——编写第一个驱动
Linux驱动入门——编写第一个驱动
Linux驱动入门——编写第一个驱动
|
2天前
|
网络协议 Shell Linux
LabVIEW 在NI Linux实时设备上访问Shell
LabVIEW 在NI Linux实时设备上访问Shell
|
2天前
|
Linux
linux驱动层输出dev_dbg打印信息
linux驱动层输出dev_dbg打印信息
24 0
|
2天前
|
Ubuntu 算法 Linux
嵌入式Linux的学习误区
该文指出了学习嵌入式Linux开发的两个常见误区。一是过分专注于学习桌面或服务器版Linux,而非关注嵌入式开发本身,实际上只需熟悉基本操作即可。二是试图在没有基础的情况下直接阅读Linux内核源代码,这是不切实际的,应先建立基础知识再进行源码学习。文章还提到了在嵌入式系统中获取和处理屏幕数据的示例,包括使用gsnap工具将framebuffer数据转为图像,以及涉及的交叉编译过程。
11 0
|
2天前
|
存储 监控 Linux
【专栏】在 Linux 中,了解已安装驱动器是系统管理的关键
【4月更文挑战第28天】在 Linux 中,了解已安装驱动器是系统管理的关键。本文介绍了三种方法:1) 使用 `lsblk` 命令显示设备名、大小和类型;2) `fdisk -l` 命令提供详细分区信息;3) `gnome-disks` 等系统管理工具展示驱动器信息。此外,还讨论了驱动器类型识别、挂载点概念及其应用。通过这些方法,用户能有效地监控和管理 Linux 系统中的驱动器。
|
2天前
|
存储 Linux
如何查看Linux设备的硬盘信息?
【4月更文挑战第12天】在Linux系统中,查看硬盘信息的常用命令。
32 4