3. unlocked_ioctl接口实现
(1)为什么要实现xxx_ioctl ?
前面我们在驱动中已经实现了读写接口,通过这些接口我们可以完成对设备的读写。但是很多时候我们的应用层工程师除了要对设备进行读写数据之外,还希望可以对设备进行控制。例如:针对串口设备,驱动层除了需要提供对串口的读写之外,还需提供对串口波特率、奇偶校验位、终止位的设置,这些配置信息需要从应用层传递一些基本数据,仅仅是数据类型不同。
通过xxx_ioctl函数接口,可以提供对设备的控制能力,增加驱动程序的灵活性。
(2)如何实现xxx_ioctl函数接口?
增加xxx_ioctl函数接口,应用层可以通过ioctl系统调用,根据不同的命令来操作dev_fifo。
kernel 2.6.35 及之前的版本中struct file_operations 一共有3个ioctl :ioctl,unlocked_ioctl和compat_ioctl 现在只有unlocked_ioctl和compat_ioctl 了
在kernel 2.6.36 中已经完全删除了struct file_operations 中的ioctl 函数指针,取而代之的是unlocked_ioctl 。
· 2.6.36 之前的内核
long (ioctl) (struct inode node ,struct file* filp, unsigned int cmd,unsigned long arg)
· 2.6.36之后的内核
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
参数cmd: 通过应用函数ioctl传递下来的命令
先来看看应用层的ioctl和驱动层的xxx_ioctl对应关系:
<1>应用层ioctl参数分析
int ioctl(int fd, int cmd, ...);
参数:
@fd:打开设备文件的时候获得文件描述符
@ cmd:第二个参数:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
@第三个参数: "..."在C语言中,很多时候都被理解成可变参数。
返回值
成功:0
失败:-1,同时设置errno
小贴士:
当我们通过ioctl调用驱动层xxx_ioctl的时候,有三种情况可供选择:
1: 不传递数据给xxx_ioctl
2: 传递数据给xxx_ioctl,希望它最终能把数据写入设备(例如:设置串口的波特率)
3: 调用xxxx_ioctl希望获取设备的硬件参数(例如:获取当前串口设备的波特率)
这三种情况中,有些时候需要传递数据,有些时候不需要传递数据。在C语言中,是
无法实现函数重载的。那怎么办?用"..."来欺骗编译器了,"..."本来的意思是传
递多参数。在这里的意思是带一个参数还是不带参数。
参数可以传递整型值,也可以传递某块内存的地址,内核接口函数必须根据实际情况
提取对应的信息。
<2>驱动层xxx_ioctl参数分析
long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
参数:
@file: vfs层为打开字符设备文件的进程创建的结构体,用于存放文件的动态信息
@ cmd: 用户空间传递的命令,可以根据不同的命令做不同的事情
@第三个参数: 用户空间的数据,主要这个数据可能是一个地址值(用户空间传递的是一个地址),也可能是一个数值,也可能没值
返回值
成功:0
失败:带错误码的负值
<3>如何确定cmd 的值。
该值主要用于区分命令的类型,虽然我只需要传递任意一个整型值即可,但是我们尽量按照内核规范要求,充分利用这32bite的空间,如果大家都没有规矩,又如何能成方圆?
现在我就来看看,在Linux 内核中这个cmd是如何设计的吧!
具体含义如下:
设备类型类型或叫幻数,代表一类设备,一般用一个字母或者1个8bit的数字序列号代表这个设备的第几个命令方 向表示是由内核空间到用户空间,或是用户空间到内核空间,入:只读,只写,读写,其他数据尺寸表示需要读写的参数大小
由上可以一个命令由4个部分组成,每个部分需要的bite都不完全一样,制作一个命令需要在不同的位域写不同的数字,Linux 系统已经给我们封装好了宏,我们只需要直接调用宏来设计命令即可。
在这里插入图片描述
通过Linux 系统给我们提供的宏,我们在设计命令的时候,只需要指定设备类型、命令序号,数据类型三个字段就可以了。
Linux 系统中已经设计了一场用的命令,可以通过查阅Linux 源码中的Documentation/ioctl/ioctl-number.txt文件,看哪些命令已经被使用过了。
<4> 如何检查命令?
可以通过宏_IOC_TYPE(nr)来判断应用程序传下来的命令type是否正确;
可以通过宏_IOC_DIR(nr)来得到命令是读还是写,然后再通过宏access_ok(type,addr,size)来判断用户层传递的内存地址是否合法。
使用方法如下:
if(_IOC_TYPE(cmd)!=DEV_FIFO_TYPE){
pr_err("cmd %u,bad magic 0x%x/0x%x.",cmd,_IOC_TYPE(cmd),DEV_FIFO_TYPE);
return-ENOTTY;
}
if(_IOC_DIR(cmd)&_IOC_READ)
ret=!access_ok(VERIFY_WRITE,(void __user*)arg,_IOC_SIZE(cmd));
else if( _IOC_DIR(cmd)&_IOC_WRITE )
ret=!access_ok(VERIFY_READ,(void __user*)arg,_IOC_SIZE(cmd));
if(ret){
pr_err("bad access %ld.",ret);
return-EFAULT;
}
5 注册cdev
定义好file_operations结构体,就可以通过函数cdev_init()、cdev_add()注册字符设备驱动了。
实例如下:
static struct cdev cdev;
cdev_init(&cdev,&hello_ops);
error = cdev_add(&cdev,devno,1);
注意如果使用了函数register_chrdev(),就不用了执行上述操作,因为该函数已经实现了对cdev的封装。
五、实例
千言万语,全部汇总在这一个图里,大家可以对照相应的层次来学习。
六、实例
好了,现在我们可以来实现一个完整的字符设备框架的实例,包括打开、关闭、读写、ioctrl、自动创建设备节点等功能。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "dev_fifo_head.h"
//指定的主设备号
#define MAJOR_NUM 250
//自己的字符设备
struct mycdev
{
int len;
unsigned char buffer[50];
struct cdev cdev;
};
MODULE_LICENSE("GPL");
//设备号
static dev_t dev_num = {0};
//全局gcd
struct mycdev *gcd;
//设备类
struct class *cls;
//获得用户传递的数据,根据它来决定注册的设备个数
static int ndevices = 1;
module_param(ndevices, int, 0644);
MODULE_PARM_DESC(ndevices, "The number of devices for register.");
//打开设备
static int dev_fifo_open(struct inode *inode, struct file *file)
{
struct mycdev *cd;
printk("dev_fifo_open success!");
//用struct file的文件私有数据指针保存struct mycdev结构体指针
cd = container_of(inode->i_cdev,struct mycdev,cdev);
file->private_data = cd;
return 0;
}
//读设备
static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t
size, loff_t *ppos)
{
int n;
int ret;
char *kbuf;
struct mycdev *mycd = file->private_data;
printk("read *ppos : %lld",*ppos);
if(*ppos == mycd->len)
return 0;
//请求大大小 > buffer剩余的字节数 :读取实际记得字节数
if(size > mycd->len - *ppos)
n = mycd->len - *ppos;
else
n = size;
printk("n = %d",n);
//从上一次文件位置指针的位置开始读取数据
kbuf = mycd->buffer + *ppos;
//拷贝数据到用户空间
ret = copy_to_user(ubuf,kbuf, n);
if(ret != 0)
return -EFAULT;
//更新文件位置指针的值
*ppos += n;
printk("dev_fifo_read success!");
return n;
}
//写设备
static ssize_t dev_fifo_write(struct file *file, const char __user *ubuf,size_t size, loff_t *ppos)
{
int n;
int ret;
char *kbuf;
struct mycdev *mycd = file->private_data;
printk("write *ppos : %lld",*ppos);
//已经到达buffer尾部了
if(*ppos == sizeof(mycd->buffer))
return -1;
//请求大大小 > buffer剩余的字节数(有多少空间就写多少数据)
if(size > sizeof(mycd->buffer) - *ppos)
n = sizeof(mycd->buffer) - *ppos;
else
n = size;