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

11 篇文章 0 订阅
11 篇文章 0 订阅

目录

1、unlocked _ioctl接口作用

2、ioctl系统调用

3、unlocked_ioctl接口驱动模板

4、unlocked_ioctl接口测试体验

4.1 unlocked_ioctl接口

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

5.1 接口命令规则

5.2 ioctl系统调用控制led

 

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;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丘比特惩罚陆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值