简介
一个字符设备驱动通常会实现常规的open
、release
、read
和write
接口,但是如果需要扩展新的功能,通常以ioctl
接口的方式实现。
用户空间的ioctl
#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...) ;
参数 | 描述 |
---|---|
fd | 文件描述符 |
cmd | 交互协议,设备驱动将根据cmd执行对应操作 |
… | 可变参数arg,依赖cmd指定的长度以及类型 |
返回值 | 执行成功时返回0,失败则返回-1 |
驱动中的ioctl
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
在新版内核中,unlocked_ioctl()与compat_ioctl()取代了ioctl()。unlocked_ioctl(),顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl(),compat全称compatible(兼容的),主要目的是为64位系统提供32位ioctl的兼容方法,也是在无大内核锁的情况下调用。
在字符设备驱动开发中,一般情况下只要实现unlocked_ioctl()即可,因为在vfs层的代码是直接调用unlocked_ioctl()。
ioctl命令,用户与驱动之间的协议
前文提到ioctl方法第二个参数cmd为用户与驱动的“协议”,理论上可以为任意int型数据,可以为0、1、2、3……,但是为了确保该“协议”的唯一性,ioctl命令应该使用更科学严谨的方法赋值,在linux中,提供了一种ioctl命令的统一格式,将32位int型数据划分为四个位段,如下图所示:
在内核中,提供了宏接口以生成上述格式的ioctl命令:
// include/uapi/asm-generic/ioctl.h#define _IOC(dir,type,nr,size) \(((dir) << _IOC_DIRSHIFT) | \((type) << _IOC_TYPESHIFT) | \((nr) << _IOC_NRSHIFT) | \((size) << _IOC_SIZESHIFT))
- dir(direction),ioctl命令访问模式(数据传输方向),占据2bit,可以为_IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
- type(device type),设备类型,占据8bit,在一些文献中翻译为“幻数”或者“魔数”,可以为任意char型字符,例如‘a’、‘b’、‘c’等等,其主要作用是使ioctl命令有唯一的设备标识。**tips:**Documentions/ioctl-number.txt记录了在内核中已经使用的“魔数”字符,为避免冲突,在自定义ioctl命令之前应该先查阅该文档。
- nr(number),命令编号/序数,占据8bit,可以为任意unsigned char型数据,取值范围0~255,如果定义了多个ioctl命令,通常从0开始编号递增;
- size,涉及到ioctl第三个参数arg,占据13bit或者14bit(体系相关,arm架构一般为14位),指定了arg的数据类型及长度,如果在驱动的ioctl实现中不检查,通常可以忽略该参数。
通常而言,为了方便会使用宏_IOC()衍生的接口来直接定义ioctl命令:
// include/uapi/asm-generic/ioctl.h/* used to create numbers */
#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)))
同时,内核还提供了反向解析ioctl命令的宏接口:
// include/uapi/asm-generic/ioctl.h/* used to decode ioctl numbers */
#define _IOC_DIR(cmd) (((cmd) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(cmd) (((cmd) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(cmd) (((cmd) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(cmd) (((cmd) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
示例
#ifndef __HAPTCIS_IOCTL_H__
#define __HAPTCIS_IOCTL_H__/** @Date: 2024-10-12 15:53:37* @LastEditors: zdk* @LastEditTime: 2024-10-14 11:47:49* @FilePath: \kernel\drivers\haptics\haptics_ioctl.h*/#include <linux/ioctl.h>
// #include <sys/ioctl.h> // 用户空间/*这里使用ioctl定义两个协议,读寄存器和写寄存器
*用户空间和内核空间共用的头文件,包含ioctl命令及相关宏定义,可以理解为一份“协议”文件
*/
//cmd中的type
#define DEVICE_TYPE 'H'
#define HAPTICS_READ_REG_NR (0)
#define HAPTICS_WRITE_REG_NR (1)
#define HAPTICS_READ_REG _IOR(DEVICE_TYPE,HAPTICS_READ_REG_NR,ioctl_protocol_t *)
#define HAPTCIS_WRITE_REG _IOW(DEVICE_TYPE,HAPTICS_WRITE_REG_NR,ioctl_protocol_t *)typedef struct
{uint8_t reg_addr;uint8_t reg_value;
}ioctl_protocol_t;#endif
这个头文件是共用的,因为他定义了协议的具体内容。
/** Silicon Integrated Co., Ltd haptic sih688x haptic driver file** Copyright (c) 2021 heater <daokuan.zhu@si-in.com>** This program is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 as published by* the Free Software Foundation*/#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include "haptics_ioctl.h"#define HAPTICS_MISC_DEV_NAME "haptics"//打开设备
static int haptics_open(struct inode* inode,struct file * filp)
{printk("%s\n",__FUNCTION__);return 0;
}//关闭设备
static int haptics_release(struct inode* inode ,struct file* filp)
{printk("%s\n",__FUNCTION__);return 0;
}//ioctl
static long haptics_ioctl(struct file * filp, unsigned int cmd, unsigned long arg)
{int ret = 0;ioctl_protocol_t msg;//反解cmd中的字段int type = _IOC_TYPE(cmd);int dir = _IOC_DIR(cmd);int nr = _IOC_NR(cmd);int size = _IOC_SIZE(cmd);printk("dir=%d size=%d\n",dir,size);//检验cmd是否正确//1.校验cmd_typeif(DEVICE_TYPE != type){printk(KERN_ERR "cmd type error\n");ret =-1;return ret;}if(HAPTICS_READ_REG == cmd)//读寄存器{//校验nrif(HAPTICS_READ_REG_NR == nr){ret = copy_from_user(&msg, (ioctl_protocol_t __user *)arg, sizeof(ioctl_protocol_t));printk("read_reg:%#02x\n",msg.reg_addr);msg.reg_value=0xff;//这里模拟读寄存器的值//将读取到的值传给用户空间ret = copy_to_user((ioctl_protocol_t __user *)arg, &msg, sizeof(ioctl_protocol_t));}}else if(HAPTCIS_WRITE_REG == cmd)//写寄存器{//校验nrif(HAPTICS_WRITE_REG_NR == nr){ret = copy_from_user(&msg, (ioctl_protocol_t __user *)arg, sizeof(ioctl_protocol_t));printk("write_reg:%#02x=%#02x\n",msg.reg_addr,msg.reg_value);//模拟写寄存器的值}}else//{printk(KERN_ERR "unknown cmd\n");}return ret;
}static struct file_operations haptics_fops=
{.owner = THIS_MODULE,.open = haptics_open,.release = haptics_release,.unlocked_ioctl = haptics_ioctl,
};struct miscdevice mdev =
{.minor = MISC_DYNAMIC_MINOR,.name = HAPTICS_MISC_DEV_NAME,.fops = &haptics_fops,
};//定义一个杂项设备结构体static int __init haptics_init(void)
{int ret = 0;//内核层只能使用printk,不能使用printfprintk(KERN_EMERG "%s\n",__FUNCTION__); //输出等级为0ret = misc_register(&mdev);if(0 == ret){printk(KERN_EMERG "misc_register ok minor=%d\n",mdev.minor);}return 0;
}static void __exit haptics_exit(void)
{misc_deregister(&mdev);printk(KERN_EMERG "%s\n",__FUNCTION__); //输出等级为0
}module_init(haptics_init);//驱动入口
module_exit(haptics_exit);//驱动出口MODULE_AUTHOR("<daokuan.zhug@si-in.com>");//声明作者信息
MODULE_DESCRIPTION("Haptics Driver V1.0.0"); //对这个模块作一个简单的描述
MODULE_LICENSE("GPL v2");//声明开源许可证// "GPL" 是指明 这是GNU General Public License的任意版本// “GPL v2” 是指明 这仅声明为GPL的第二版本
上面是驱动代码,负责解析ioctl
命令。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include "haptics_ioctl.h"
/*
argc:应用程序参数个数,包括应用程序本身
argv[]:具体的参数内容,字符串形式
./main <filename> <r:w> r表示读 w表示写
*/int main(int argc,char * argv[])
{ioctl_protocol_t msg;char* filename;int fd=0;if(argc!=3){printf("error usage\n");return -1;}filename=argv[1];fd = open(filename,O_RDWR);if(fd<0){printf("can not open file %s\n",filename);return -2;}if(0 == strcmp(argv[2],"r")){msg.reg_addr = 0x01;ioctl(fd,HAPTICS_READ_REG,&msg);printf("read %#02x=%#02x\n", msg.reg_addr,msg.reg_value);}else if(0 == strcmp(argv[2],"w")){msg.reg_addr = 0x01;msg.reg_value = 0xff;ioctl(fd,HAPTCIS_WRITE_REG,&msg);}else{printf("error usage\n");}close(fd);
}
上面是应用层代码,负责发送ioctl
消息。
注意:
_IOR
中第三个参数size是参数的大小,用于驱动中解析使用,但是如果是固定长度,这个参数也没有用到。理论上使用_IOR
、_IOW
或_IO
是没有严格区别的,所以我们上述代码中可以改为\#define HAPTICS_READ_REG _IO(DEVICE_TYPE,HAPTICS_READ_REG_NR)
ioctl
函数中第三个参数,表示传入驱动的实际参数,可以是一个整型值,也可以是一个结构体指针。我们示例中是传入的结构体指针。