linux驱动开发之字符设备--内核和用户空间数据的交换(ioctl)

前言

在驱动中,除了需要具备读写能力外,还需要对硬件设备进行控制。ioctl就常用户底层的一些操作。

正文

linux中,建议使用下边的方式,进行进行ioctl命令

设备类型序列号方向数据尺寸
8bit8bit2bit13/14bit

命令码的设备类型为一个 “幻数”,可以是在 0 ~0xff 之间值,内核中的 ioctl-number.txt 给出了一些推荐的和已经被使用的 “幻数”。

命令码列号也是 8 位宽 。

命令码的方向字节也是 2 位宽,该字端表示出具传送的方向,可能的值是 _IO_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)、_IOC_READ|_IOC_WRITE(双向)。数据传输的方向是从应用程序的角度来看的

命令码的数据长度字节表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常为 13 或 14 位。

内核中定义了 _IO()、_IOR()、_IOW()、_IOWR()这4个宏来辅助生成命令。

#define _IOC(dir,type,nr,size) \
        (((dir)  << _IOC_DIRSHIFT) | \
         ((type) << _IOC_TYPESHIFT) | \
         ((nr)   << _IOC_NRSHIFT) | \
         ((size) << _IOC_SIZESHIFT))
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

以上宏的作用是根据输入的type(设备类型)、nr(序列号)、size(数据长度) 和宏中的位移来生成相应的 命令码。

#define _IOC_SIZE(nr)   \
 ((((((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) & (_IOC_WRITE|_IOC_READ)) == 0)?    \
                         0: (((nr) >> _IOC_SIZESHIFT) & _IOC_XSIZEMASK))

获得数据域的大小。

应用API

  #include <sys/ioctl.h>
  int ioctl(int d, int request, ...)

参数:fd : 打开的文件描述符
request:命令码
可变参数:可以用来表示,写时用来传入驱动的数据,读时用来接收驱动的数据

内核

    long (*unlocked_ioctl) (struct file *filp , unsigned int, cmd .unsigned long arg);

参数:filp 应用打开的文件在内核的表示
cmd: 对应应用程序传入的命令码
arg : 对于应用程序 传入或者接收的数据

实例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/cdev.h>
#include <linux/fs.h>

#include <linux/uaccess.h>
//幻数
#define IOCTL_TYPE 'b'

#define COUNT   32

struct  ioctl_arg {
    int val;
    char buf[COUNT];
};
//定义的命令码
#define CMDCTL  _IO(IOCTL_TYPE,0)
#define CMDR    _IOR(IOCTL_TYPE,1,struct ioctl_arg)
#define CMDW    _IOW(IOCTL_TYPE,2,struct ioctl_arg)

#define DEVICE_NAME "cdev_demo"

static struct cdev *pdev = NULL;
static int major = 0;
static int minor = 0;
static int count = 2;


#define BUF_SIZE        (1024)
static char kbuf[BUF_SIZE];
static int  buf_count = 0;

static int cdev_demo_open(struct inode * inode, struct file * file)
{
    printk("%s,%d\n",__func__,__LINE__);
    return 0;
}
static int cdev_demo_release(struct inode *inode, struct file * file)
{
    printk("%s,%d\n",__func__,__LINE__);
    return 0;
}
static ssize_t cdev_demo_read(struct file * file, char __user * buffer, size_t size, loff_t * loff)
{
    printk("%s,%d\n",__func__,__LINE__);

    if(0 == buf_count){
        return -EAGAIN;
    }

    if(buf_count < size){
        size = buf_count;
    }

    if(size == copy_to_user(buffer,kbuf,size)){
        return -EAGAIN;
    }

    buf_count  = 0;

    return size;

}

static ssize_t cdev_demo_write(struct file * file, const char __user * buffer, size_t size, loff_t * loff)
{
    printk("%s,%d\n",__func__,__LINE__);
    printk("buffer=%s   size=%d\n",buffer,size);
    if(size >BUF_SIZE){
        return -ENOMEM;
    }
    if(size == copy_from_user(kbuf,buffer,size)){
        return -EAGAIN;
    }

    buf_count = size;
    return size;
}
//ioctl
static long cdev_demo_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
    static struct ioctl_arg buf;
    printk("%s,%d\n",__func__,__LINE__);
    //分辨不同命令码
    switch(cmd){
        case CMDCTL:
                printk("do CMDCTL\n");
                break;
        case CMDR:
            //使用 _IOC_SIZE()获得命令码中的数据长度
            if(sizeof(buf) != _IOC_SIZE(cmd)){
                return -EINVAL;
            }

            if(sizeof(buf) == copy_to_user((struct ioctl_arg*)arg,&buf,sizeof(struct ioctl_arg))){
                return -EAGAIN;
            }
            printk("do CMDR\n");
            break;
        case CMDW:
            if(sizeof(buf)!= _IOC_SIZE(cmd)){
                 return -EINVAL;
            }

            if(sizeof(buf) == copy_from_user(&buf,(struct ioctl_arg*)arg,sizeof(buf))){
                return -EAGAIN;
            }
            printk("do CMDW\n");
            printk("%d,%s \n",buf.val,buf.buf);
            break;

        default:
            break;

    }

    return 0;
}
static struct file_operations fops ={
    .owner   = THIS_MODULE,
    .open    = cdev_demo_open,
    .release = cdev_demo_release,
    .read    = cdev_demo_read,
    .write   = cdev_demo_write,
    .unlocked_ioctl = cdev_demo_ioctl,
};

static int __init cdev_demo_init(void)
{
    dev_t dev;
    int ret;

    printk("%s,%d\n",__func__,__LINE__);

    pdev = cdev_alloc();
    if(NULL == pdev){
        printk("cdev_alloc failed.\n");
        return -ENOMEM;
    }

    cdev_init(pdev,&fops);

    ret = alloc_chrdev_region(&dev,minor,count,DEVICE_NAME);
    if(ret){
        printk("alloc_chrdev_region failed.\n");
        goto ERROR_CDEV;
    }
    major = MAJOR(dev);
    ret = cdev_add(pdev, dev,count);
    if(ret) {
        printk("cdev_add failed.\n");
        goto  ERROR_ADD;
    }

    return 0;
ERROR_ADD:
    unregister_chrdev_region(dev,count);
ERROR_CDEV:
    cdev_del(pdev);
    return ret;
}
static void __exit cdev_demo_exit(void)
{
    printk("%s,%d\n",__func__,__LINE__);
    unregister_chrdev_region(MKDEV(major,minor),count);

    cdev_del(pdev);
}

module_init(cdev_demo_init);
module_exit(cdev_demo_exit);
MODULE_LICENSE("GPL");

APP:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <errno.h>

#include <sys/ioctl.h>


#define IOCTL_TYPE 'b'

#define COUNT   32

struct  ioctl_arg {
    int val;
    char buf[COUNT];
};

#define CMDCTL  _IO(IOCTL_TYPE,0)
#define CMDR    _IOR(IOCTL_TYPE,1,struct ioctl_arg)
#define CMDW    _IOW(IOCTL_TYPE,2,struct ioctl_arg)




#define BUFFER_SIZE  (1024)
int main(int argc,char* argv[])
{
    char *dev = "/dev/cdev_demo";
    int fd = open(dev,O_RDWR);
    if(0 > fd){
        perror("open\n");
        return -1;
    }
    //先构造数据,然后调用ioctl进行写操作
        printf("begin write\n");
        struct ioctl_arg arg = {
            .val = 123,
            .buf = "address",
        };

        if( 0 > ioctl(fd,CMDW,&arg)){
            perror(" write ");
            return -1;
        }

        //调用ioctl进行读取内核数据
        printf("begin read\n");
        struct ioctl_arg args;
        if(0 > ioctl(fd,CMDR,&args)){
            perror("read");
            return -1;
        }
        printf("read args.val = %d args.buf = %s\n",args.val,args.buf);

    close(fd);
    return  0;
}

运行结果:

shell@tiny4412:/mnt # ./test                                                
[  777.455766] cdev_demo_open,39
[  777.457120] do CMDW
[  777.457192] 123,address 
[  777.457238] cdev_demo_ioctl,87
[  777.457334] do CMDR
[  777.457441] cdev_demo_release,44
begin write
begin read
read args.val = 123 args.buf = address

注: [ xx] 表示tiny4412打印的信息,不带的表示app 打印出的信息。

总结

在ioctl中,可以根据命令码对硬件进行功能的操作,即可以进行写操作也可以进行读操作。这样就比read()、write()更加灵活。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值