首页 > 其他分享 >字符设备驱动-13.ioctl命令详解

字符设备驱动-13.ioctl命令详解

时间:2023-08-21 22:14:20浏览次数:39  
标签:13 int cmd unsigned ioctl 详解 arg IOC

1 引入ioctl

一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能增添命令,通常以增设 ioctl() 命令的方式实现。
对于ioctl这个系统调用接口,Linux的创始人在2.0版本之前并没有进行添加,仅有write和read两个接口,但是后来发现当需要去控制文件的某些操作的时候,很显然这两个接口根本不够用。所以才有了这个万能控制接口ioctl,但是作为Linux的创始人Linus本人一直排斥该接口,因为这个ioctl接口的在内核中的使用相当于对应用层开设了一个能够直接交互的窗口,很影响内核整体的权限控制,不过由于目前还暂时没有更好可以替代的方法,所以还是继续保留了这个接口的使用。
image

2 用户空间 ioctl

image

int ioctl(int fd, int request, …);
//eg:
ret = ioctl(fd, MYCMD);
if (ret == -1) {
	printf("ioctl: %s\n", strerror(errno));
}

函数功能:
1.向硬件设备发送控制命令
2.还可以和硬件设备进行读或者写操作
参数:
fd:文件描述符
request:给硬件设备发送的控制命令
arg:保存的就是用户缓冲区的首地址
返回值:执行成功返回0,执行失败返回-1, ioctl 最常见的 errorno 值为 ENOTTY(error not a typewriter)表示命令找不到。

3 内核空间 ioctl

image

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

4 IOCTL的命令构成

ioctl命令就是用户和驱动约定的一种协议, 理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:
image

1. dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
2. size,涉及到 ioctl 函数第三个参数 arg ,占据14bit,指定了 arg 的数据类型及长度;
3. type(device type),设备类型,占据 8 bit,可以为任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
4. nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增

通常而言,为了方便会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令:

//ioctl.h
#define _IOC_NRBITS	8
#define _IOC_TYPEBITS	8

/*
 * Let any architecture override either of the following before
 * including this file.
 */

#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS	14
#endif

#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS	2
#endif

#define _IOC_NRMASK	((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK	((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK	((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK	((1 << _IOC_DIRBITS)-1)

#define _IOC_NRSHIFT	0
#define _IOC_TYPESHIFT	(_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT	(_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT	(_IOC_SIZESHIFT+_IOC_SIZEBITS)

/*
 * Direction bits, which any architecture can choose to override
 * before including this file.
 *
 * NOTE: _IOC_WRITE means userland is writing and kernel is
 * reading. _IOC_READ means userland is reading and kernel is writing.
 */

#ifndef _IOC_NONE
# define _IOC_NONE	0U
#endif

#ifndef _IOC_WRITE
# define _IOC_WRITE	1U
#endif

#ifndef _IOC_READ
# define _IOC_READ	2U
#endif

#define _IOC(dir,type,nr,size) \
	(((dir)  << _IOC_DIRSHIFT) | \
	 ((type) << _IOC_TYPESHIFT) | \
	 ((nr)   << _IOC_NRSHIFT) | \
	 ((size) << _IOC_SIZESHIFT))

#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif

/*
 * Used to create numbers.
 *
 * NOTE: _IOW means userland is writing and kernel is reading. _IOR
 * means userland is reading and kernel is writing.
 */
#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size)	_IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr)		(((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)		(((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)		(((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)		(((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

/* ...and for the drivers/sound files... */

#define IOC_IN		(_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT		(_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT	((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK	(_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT	(_IOC_SIZESHIFT)

image
image
除了_IO/_IOR/_IOW/_IOW等命令外,还支持反向解析 ioctl 命令的宏接口:主要就是利用Mask看是否4个位段是否越界,如果越界说明cmd构造的不合法:

#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

举个例子比如image,展开命令成一个unsigned int cmd整数为:(字符D的ascii码为68)

0<<30 | 0<<16 | 68<<8 | 0x7<<0

5 ioctl系统调用过程详解

5.1 示例

fd = open("/dev/ioctl-test", O_RDWR);
ioctl(fd, IOCINIT);
/* 往寄存器0x01写入数据0xef */
memset(&my_msg, 0, sizeof(my_msg));
my_msg.addr = 0x01;
my_msg.data = 0xef;
ioctl(fd, IOCWREG, &my_msg);
/* 读寄存器0x01 */
memset(&my_msg, 0, sizeof(my_msg));
my_msg.addr = 0x01;
ret = ioctl(fd, IOCRREG, &my_msg);

驱动示例:

#define IOC_MAGIC 'c'
#define IOCINIT _IO(IOC_MAGIC, 0)
#define IOCRREG _IOR(IOC_MAGIC, 1, int)
#define IOCWREG _IOW(IOC_MAGIC, 2, int)//定义3个cmd
#define IOC_MAXNR 3

struct msg {
	int addr;
	unsigned int data;
};

static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct msg my_msg;
	/* 检查设备类型 */
	if (_IOC_TYPE(cmd) != IOC_MAGIC) {
		pr_err("[%s] command type [%c] error!\n", __func__, _IOC_TYPE(cmd));
		return -ENOTTY; 
	}
	/* 检查序数 */
	if (_IOC_NR(cmd) > IOC_MAXNR) {
		pr_err("[%s] command numer [%d] exceeded!\n", __func__, _IOC_NR(cmd));
		return -ENOTTY;
	}
	/* 检查访问模式 */
	if (_IOC_DIR(cmd) & _IOC_READ)
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
		ret= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
#else
		ret= !access_ok((void __user *)arg, _IOC_SIZE(cmd));
#endif
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
		ret= !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
#else
		ret= !access_ok((void __user *)arg, _IOC_SIZE(cmd));
#endif
	if (ret)
		return -EFAULT;
	switch(cmd) { 
	/* 初始化设备 */
	case IOCINIT:
		break;
	/* 读寄存器 */
	case IOCRREG:
		ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg));
		if (ret) 
			return -EFAULT;
		msg->data = read_reg(msg->addr);
		ret = copy_to_user((struct msg __user *)arg, &msg, sizeof(my_msg));
		if (ret) 
			return -EFAULT;
		break;
	/* 写寄存器 */
	case IOCWREG:
		ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg));
		if (ret) 
			return -EFAULT;
		write_reg(msg->addr, msg->data);
		break;
	default:
		return -ENOTTY;
	}
	return 0;
}

image
首先定好3个命令,通过arg传入要写入的地址和数据or 要读的地址。然后检查type是否为‘c’, 检查命令号是否超过最大值3,检查方向是读还是写,利用access_ok判断用户地址是否可以访问。
image
最后将用户地址arg的数据透过copy_from_user和copy_to_user进行拷贝。然后进行寄存器读写。

5.2 ioctl过程详解

在系统调用中,是通过SWI(Software Interrupt)的方式陷入内核态的, 首先通过软中断方式切换到内核态,ioctl的系统调用位于arch/arm/include/asm/unistd.h:

#define __NR_ioctl	(__NR_SYSCALL_BASE+ 54)

arch/arm/kernel/calls.S

/* 55 */	CALL(sys_ioctl);

调用sys_ioctl()

/include/linux.h
	asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg);

然后调用SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)

/include/linux/syscalls.h
#define SYSCALL_DEFINE3(name, ...)  SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...)	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...)	asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

SYSCALL_DEFINE3
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)就是sys_ioctl的定义:
image
fget_light() 以及 security_file_ioctl() 就是检验可操作安全性,所以sys_ioctl更多是调用更深一层接口 do_vfs_ioctl()

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
	struct file *filp;
	int error = -EBADF;
	int fput_needed;

	filp = fget_light(fd, &fput_needed);//由fd得带filp指针
	if (!filp)
		goto out;

	error = security_file_ioctl(filp, cmd, arg);
	if (error)
		goto out_fput;

	error = do_vfs_ioctl(filp, fd, cmd, arg);
 out_fput:
	fput_light(filp, fput_needed);
 out:
	return error;
}

do_vfs_ioctl函数
image

int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
		 unsigned long arg)
{
	int error = 0;
	int __user *argp = (int __user *)arg;
	struct inode *inode = filp->f_path.dentry->d_inode;

	switch (cmd) {
	case FIOCLEX:
		set_close_on_exec(fd, 1);
		break;

	case FIONCLEX:
		set_close_on_exec(fd, 0);
		break;

	case FIONBIO:
		error = ioctl_fionbio(filp, argp);
		break;

	case FIOASYNC:
		error = ioctl_fioasync(fd, filp, argp);
		break;

	case FIOQSIZE:
		if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode) ||
			S_ISLNK(inode->i_mode)) {
			loff_t res = inode_get_bytes(inode);
			error = copy_to_user(argp, &res, sizeof(res)) ?
					-EFAULT : 0;
		} else
			error = -ENOTTY;
		break;

	case FIFREEZE:
		error = ioctl_fsfreeze(filp);
		break;

	case FITHAW:
		error = ioctl_fsthaw(filp);
		break;

	case FS_IOC_FIEMAP:
		return ioctl_fiemap(filp, arg);

	case FIGETBSZ:
		return put_user(inode->i_sb->s_blocksize, argp);

	default:
		if (S_ISREG(inode->i_mode))//是否为常规文件若是常规文件
			error = file_ioctl(filp, cmd, arg);
		else
			error = vfs_ioctl(filp, cmd, arg);//调用vfs_ioctl
		break;
	}
	return error;
}

vfs_ioctl()
image

static long vfs_ioctl(struct file *filp, unsigned int cmd,
			  unsigned long arg)
{
	int error = -ENOTTY;

	if (!filp->f_op || !filp->f_op->unlocked_ioctl)
		goto out;
unlocked_ioctl
	error = filp->f_op->unlocked_ioctl(filp, cmd, arg);//调用unlocked_ioctl()
	if (error == -ENOIOCTLCMD)
		error = -EINVAL;
 out:
	return error;
}	

标签:13,int,cmd,unsigned,ioctl,详解,arg,IOC
From: https://www.cnblogs.com/fuzidage/p/17645253.html

相关文章

  • 「NOIP2013」货车运输 题解
    「NOIP2013」货车运输前言这道题算是一个稍有思维难度的MST+LCA题目了。稍微卡了一会(0-88-88-88-100(打表)-100(打表)-100(正解)),开始是打了表过了,后面在DCZ的帮助下正解通过(下面注释提到的一个坑)。题目大意给出一张无向图\(G\),有\(n\)个点和\(m\)个边\((x,y)=z\),找到一......
  • 设计模式和七大原则概述及单一职责原则详解
    设计模式的目的编写软件过程中,程序员面临着来自,耦合性,内聚性以及可维护性,扩展性,重用性等方面的挑战。设计模式是为了让程序,具有更好的1.代码重用性(相同代码,不用重复编写)2.可读性(编程规范性,便于其他程序员的阅读和理解)3.可扩展性(当需要增加新的功能时,非常的方便)4.可靠......
  • 自我介绍:20231301 周子昂
    自我介绍大家好!我叫周子昂。#(0)照片---#(1)形容词##周:思绪“周”密,严谨踏实###做事之前喜欢整体思考,有一定的布局。尽可能在做事时脚踏实地,认真仔细。事后反思总结经验教训,以便下次更好。##子:谦谦君“子”,温和有礼###待人接物有基本的礼仪与尊重。......
  • RunnerGo中WebSocket、Dubbo、TCP/IP三种协议接口测试详解
    大家好,RunnerGo作为一款一站式测试平台不断为用户提供更好的使用体验,最近得知RunnerGo新增对,WebSocket、Dubbo、TCP/IP,三种协议API的测试支持,本篇文章跟大家分享一下使用方法。WebSocket协议WebSocket是一种在单个TCP连接上进行全双工通信的API技术。相比于传统的HTTP请求,We......
  • t113-c-线程、锁、信号
    线程:这个之前的文章已经记录过了线程之间的通信(同步)https://blog.csdn.net/weixin_56187542/article/details/126251049锁:c的锁同样是pthread头文件里面的https://blog.csdn.net/shaosunrise/article/details/107620885创建线程和加锁:信号这个是用来通知线程该工作了,不......
  • RunnerGo中WebSocket、Dubbo、TCP/IP三种协议接口测试详解
    大家好,RunnerGo作为一款一站式测试平台不断为用户提供更好的使用体验,最近得知RunnerGo新增对,WebSocket、Dubbo、TCP/IP,三种协议API的测试支持,本篇文章跟大家分享一下使用方法。WebSocket协议WebSocket是一种在单个TCP连接上进行全双工通信的API技术。相比于传统的HTTP请......
  • 20天 hot 100 速通计划-day13
    回溯131.分割回文串给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文串。返回s所有可能的分割方案。回文串是正着读和反着读都一样的字符串。示例1:输入:s="aab"输出:[["a","a","b"],["aa","b"]]示例2:输入:s="a"输出:[["a"]]提示:1&......
  • 数论-同余与扩展欧几里得详解(附例题及代码)
    数论-同余与扩展欧几里得详解(附例题及代码)注意:这篇文章的信息量会有一点多,请耐心看完一.同余1.1同余的定义给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(modm)简单来说,对于x,y,若x%p=y%p,即x,y对于p的余数......
  • 永嘉原厂超强抗干扰VK36N系列 1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20键
      概述.VK36N1D具有1个触摸按键,可用来检测外部触摸按键上人手的触摸动作。该芯片具有较高的集成度,仅需极少的外部组件便可实现触摸按键的检测。提供了1个1对1输出脚,可通过IO脚选择上电输出电平,有直接输出和锁存输出2个型号可选。芯片内部采用特殊的集成电路,具有高电源电压......
  • 北大ACM poj3913 Gnome Sequencing
    GnomeSequencingTimeLimit:1000MS MemoryLimit:65536KTotalSubmissions:1267 Accepted:865DescriptionInthebookAllCreaturesofMythology,gnomesarekind,beardedcreatures,whilegoblinstendtobebossyandsimple-minded.Thegoblinslike......