Linux驱动--IOCTL实现

参考:[Linux]实现设备驱动的ioctl函数_哔哩哔哩_bilibili、《Linux设备驱动程序(中文第三版).pdf》

1 用户空间ioctl

用户空间的ioctl函数原型,参数是可变参数,实际为单个可选的参数:

#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...);

2 内核驱动ioctl

字符设备的文件操作集中的ioctl实现,函数原型:

/* 参数filp:文件描述符指针
 * 参数cmd:用户空间传递的命令
 * 参数arg:对应命令的参数
 * 返回值:通常返回-EINVAL,表示一个无效的ioctl命令。
*/
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);

KO是64位,应用程序是32位的时候,使用的是compat_ioctl函数。

参数cmd是32位整型,不是简单的数字,其在系统中应该是唯一的,其构成如下:

include/uapi/asm-generic/ioctl.h

---------------------------------------
| direction |  size  |  type | number |
---------------------------------------
|    2bits  | 14bits | 8bits |  8bits |
---------------------------------------

direction:表示方向,如果命令涉及数据传输,可能的值为_IOC_NONE(没有数据传输)、_IOC_WRITE、_IOC_READ、_IOC_WRITE|_IOC_READ,数据传输从应用程序的角度看,_IOC_READ表示从设备读,也就是设备写到用户空间。

# define _IOC_NONE	0U
# define _IOC_WRITE	1U
# define _IOC_READ	2U

size:参数数据大小,位宽是依赖体系,通常是13或14位,可通过宏_IOC_SIZEBITS找到值。

# define _IOC_SIZEBITS	14

type:幻数,只是选择了一个数(参考Documentation/ioctl/ioctl-number.txt)并且在驱动中使用它,位宽为8位。

#define _IOC_TYPEBITS	8

number:序号,位宽8位。

有些cmd已经被使用,需要先检查:Documentation/ioctl/ioctl-number.txt

2.1 命令号创建

可通过内核定义的宏来创建命令号:

/* 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)))

另外,内核还提供了解码命令号的宏:

/* 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)

3 实验

3.1 驱动和APP

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/device.h>
#include "test_ioctl.h"

#define GLOBALMEM_BUFFER_SIZE  0x1000
#define GLOBALMEM_DEVICE_NUM   2
#define GLOBALMEM_NAME  "globalmem"

struct globalmem_dev {
    struct cdev cdev;
    dev_t devid;                /* 设备号,高12位是主设备号,低20位是次设备号 */
    int major;                  /* 主设备号 */
    int minor;                  /* 次设备号 */
    struct class *class;        /* 类 */
    struct device *device;      /* 设备 */
    char buffer[GLOBALMEM_BUFFER_SIZE];   /* 缓存 */
    int cur_chars;     /* 缓存中当前字符个数 */
};

struct globalmem_dev *globalmem_devp;

int globalmem_open(struct inode *inode, struct file *filp)
{
    struct globalmem_dev *dev;

    // printk(KERN_INFO "%s open \n",current->comm);
    dev = container_of(inode->i_cdev, struct globalmem_dev, cdev);  //获取设备结构体的地址
    filp->private_data = dev;    //将设备结构地址放到文件描述符结构的私有数据中

    return 0;
}

ssize_t globalmem_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
    int ret = 0;
    unsigned long p = *f_pos;
    struct globalmem_dev *dev = filp->private_data;

    if (p >= GLOBALMEM_BUFFER_SIZE) {
        return 0;
    }
    if (count > GLOBALMEM_BUFFER_SIZE - p) {
        count = GLOBALMEM_BUFFER_SIZE - p;
    }

    /* 内核空间到用户空间缓存区的复制 */
    if (copy_to_user(buf, dev->buffer + p, count)) {
        return -EFAULT;
    }

    *f_pos += count;
    ret = count;
    if (dev->cur_chars - count <= 0) {
        dev->cur_chars = 0;
    } else {
        dev->cur_chars -= count;
    }
    printk(KERN_INFO "read %lu bytes(s) from %lu\n", (unsigned long)count, p);

    return ret;
}

ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
    int ret = 0;
    unsigned long p = *f_pos;
    struct globalmem_dev *dev = filp->private_data;

    if (p >= GLOBALMEM_BUFFER_SIZE) {
        return 0;
    }
    if (count > GLOBALMEM_BUFFER_SIZE - p) {
        count = GLOBALMEM_BUFFER_SIZE - p;
    }

    /* 用户空间缓存区到内核空间缓存区的复制 */
    if (copy_from_user(dev->buffer + p, buf, count)) {
        return -EFAULT;
    }

    *f_pos += count;
    ret = count;
    dev->cur_chars += count;
    printk(KERN_INFO "written %lu bytes(s) from %lu\n", (unsigned long)count, p);

    return ret;
}

long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    long retval = 0;
    int tmp = 0;
    struct globalmem_dev *dev = filp->private_data;

    /* 检查幻数(返回值POSIX标准规定,也用-EINVAL) */
    if (_IOC_TYPE(cmd) != HC_IOC_MAGIC) {
        return -ENOTTY;
    }

    /* 检查命令编号 */
    if (_IOC_NR(cmd) > HC_IOC_MAXNR) {
        return -ENOTTY;
    }

    printk(KERN_INFO "cmd(0x%x) decode, direction: %u, type: %u, number: %u, size: %u\r\n",
        cmd, _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd));

    switch(cmd) {
        case HC_IOC_RESET:
            printk(KERN_INFO "ioctl reset\n");
            memset(dev->buffer, 0, GLOBALMEM_BUFFER_SIZE);
            dev->cur_chars = 0;
            break;

        case HC_IOCP_GET_LENS:
            printk(KERN_INFO "ioctl get lens through pointer\n");
            retval = put_user(dev->cur_chars, (int __user *)arg);
            break;

        case HC_IOCV_GET_LENS:
            printk(KERN_INFO "ioctl get lens through value\n");
            return dev->cur_chars;
            break;

        case HC_IOCP_SET_LENS:
            printk(KERN_INFO "ioctl set lens through pointer");
            if (! capable (CAP_SYS_ADMIN)) {
                return -EPERM;
            }
            retval = get_user(tmp, (int __user *)arg);
            if(dev->cur_chars > tmp) {
                dev->cur_chars = tmp;
            }
            printk(KERN_INFO " %d\n", dev->cur_chars);
            break;

        case HC_IOCV_SET_LENS:
            printk(KERN_INFO "ioctl set lens through value");
            if (!capable (CAP_SYS_ADMIN)) {
                return -EPERM;
            }
            dev->cur_chars = min(dev->cur_chars, (int)arg);
            printk(KERN_INFO " %d\n", dev->cur_chars);
            break;
    }

    return retval;
}

int globalmem_release(struct inode *inode, struct file *filp)
{
    // printk(KERN_INFO "%s release\n",current->comm);
    return 0;
}

/* 字符设备的操作函数 */
struct file_operations globalmem_fops = {
    .owner   = THIS_MODULE,
    .open    = globalmem_open,
    .read    = globalmem_read,
    .write   = globalmem_write,
    .release = globalmem_release,
    .unlocked_ioctl = globalmem_ioctl,
};

static int __init globalmem_init(void)	
{
    int ret = 0;
    int i = 0;
    dev_t devid = 0;
    struct class *class = NULL;

    printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\n");
    globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * GLOBALMEM_DEVICE_NUM, GFP_KERNEL);
    if (!globalmem_devp) {
        printk(KERN_WARNING "alloc mem failed");
        return -ENOMEM;
    }
    memset(globalmem_devp, 0x0, sizeof(struct globalmem_dev) * GLOBALMEM_DEVICE_NUM);

    /* 动态分配主设备号 */
    ret = alloc_chrdev_region(&devid, 0, GLOBALMEM_DEVICE_NUM, GLOBALMEM_NAME);;
    if (ret < 0) {
        printk(KERN_WARNING "hello: can't alloc major!\n");
        goto fail;
    }

    /* 初始化cdev */
    for (i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
        globalmem_devp[i].major = MAJOR(devid);
        globalmem_devp[i].minor = i;
        globalmem_devp[i].devid = MKDEV(globalmem_devp[i].major, globalmem_devp[i].minor);
        cdev_init(&globalmem_devp[i].cdev, &globalmem_fops);
        globalmem_devp[i].cdev.owner = THIS_MODULE;
        ret = cdev_add(&globalmem_devp[i].cdev, globalmem_devp[i].devid, 1);
        if(ret) {
            printk(KERN_WARNING "fail add globalmem char device: %d\r\n", i);
        }
    }
    
    class = class_create(THIS_MODULE, "hc_dev");
    if (!class) {
        printk(KERN_WARNING "fail create class\r\n");
        ret = PTR_ERR(class);
        goto failure_class;
    }

    for(i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
        globalmem_devp[i].class = class;
        globalmem_devp[i].device = device_create(class, NULL, globalmem_devp[i].devid, NULL, "globalmem_dev%d", i);
        if (IS_ERR(globalmem_devp[i].device)) {
            ret = PTR_ERR(globalmem_devp[i].device);
            goto fail_device_create;
        }
    }

    printk(KERN_INFO "---END HELLO LINUX MODULE---\n");
    return 0;

failure_class:
    unregister_chrdev_region(devid, GLOBALMEM_DEVICE_NUM);

fail_device_create:
    class_destroy(class);
    unregister_chrdev_region(devid, GLOBALMEM_DEVICE_NUM);

fail:
    if (globalmem_devp) {
        kfree(globalmem_devp);
    }

    return ret;
}

static void __exit globalmem_exit(void)
{
    int i;
    for (i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
        device_destroy(globalmem_devp[i].class, globalmem_devp[i].devid);
    }
    class_destroy(globalmem_devp[0].class);
    
    for (i = 0; i < GLOBALMEM_DEVICE_NUM; i++) {
        cdev_del(&globalmem_devp[i].cdev);
    }

    /* 释放设备号 */
    unregister_chrdev_region(globalmem_devp[0].devid, GLOBALMEM_DEVICE_NUM);

    kfree(globalmem_devp);
    printk(KERN_INFO "GOODBYE LINUX\n");
}

module_init(globalmem_init);
module_exit(globalmem_exit);

//描述性定义
MODULE_LICENSE("GPL");
MODULE_AUTHOR("KGZ");
MODULE_VERSION("V1.0");


/*****补充     
1、compat_ioctl:支持64bit的driver必须要实现的ioctl,当有32bit的userspace application call 64bit kernel的IOCTL的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter

2、如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl
*****/
#ifndef TEST_IOCTL_H
#define TEST_IOCTL_H

#define HC_IOC_MAGIC 0x81   /* Documentation/userspace-api/ioctl/ioctl-number.rst */

/* 复位命令,直接清除值 */
#define HC_IOC_RESET        _IO(HC_IOC_MAGIC,  0)

/* 从驱动中读取数据,通过指针返回 */
#define HC_IOCP_GET_LENS    _IOR(HC_IOC_MAGIC, 1, int)

/* 从驱动中读取数据,通过返回值返回 */
#define HC_IOCV_GET_LENS    _IO(HC_IOC_MAGIC,  2)

/* 写数据到驱动,通过指针设置 */
#define HC_IOCP_SET_LENS    _IOW(HC_IOC_MAGIC, 3, int)

/* 写数据到驱动,通过值设置 */
#define HC_IOCV_SET_LENS    _IO(HC_IOC_MAGIC,  4)
 
/*正常情况不会混用,要不都按值传递,要不都用指针,这里只是演示*/

#define HC_IOC_MAXNR 4

#endif  /* TEST_IOCTL_H */
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>
#include<errno.h>
#include"test_ioctl.h"

static int check_input_para(int argc, int num)
{
    if (argc != num) {
        printf("Error Usage!\r\n");
        return -1;
    }
    return 0;
}

int main(int argc ,char* argv[])
{
    int n,retval=0;
    int fd;

    if (argc < 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    fd = open("/dev/globalmem_dev0",O_RDWR);
    switch(argv[1][0]) {
        case '0':
            ioctl(fd, HC_IOC_RESET);
            printf("reset globalmem\n");
            break;
        case '1':
            ioctl(fd, HC_IOCP_GET_LENS, &n);
            printf("app get lens pointer: %d\n", n);
            break;
        case '2':
            n = ioctl(fd, HC_IOCV_GET_LENS);
            printf("app get lens value: %d\n", n);
            break;
        case '3':
            if (check_input_para(argc, 3))
                goto error_exit;
            n=argv[2][0] - '0';
            retval = ioctl(fd,HC_IOCP_SET_LENS,&n);
            printf("set lens value, %d %s\n",n,strerror(errno));
             break;
        case '4':
            if (check_input_para(argc, 3))
                goto error_exit;
            n=argv[2][0] - '0';
            retval = ioctl(fd,HC_IOCV_SET_LENS,n);
            printf("set lens value, %d %s\n",n,strerror(errno));
             break;
    }

error_exit:
    close(fd);
    
    return 0;
}

3.2 测试

$ insmod test_ioctl_drv.ko 
[75555.254838] ---BEGIN HELLO LINUX MODULE---
[75555.255797] ---END HELLO LINUX MODULE---

$ echo 12345678 > /dev/hc_dev0
$ cat /dev/hc_dev0
12345678

$ ./test_ioctl_app 0
reset hc
cmd(0x8100) decode, direction: 0, type: 129, number: 0, size: 0
ioctl reset
$ cat /dev/hc_dev0  

$ echo 12345678 > /dev/hc_dev0  
$ ./test_ioctl_app 1
get lens pointer, 9
$ ./test_ioctl_app 2
get lens value, 9

$ ./test_ioctl_app 3 5
[  527.935609] cmd(0x40048103) decode, direction: 1, type: 129, number: 3, size: 4
[  527.935611] ioctl set lens through pointer
set lens value, 5 Success
$ cat /dev/hc_dev0
12345

$ ./test_ioctl_app 4 4 
[  570.783527] cmd(0x8104) decode, direction: 0, type: 129, number: 4, size: 0
[  570.783529] ioctl set lens through value
set lens value, 4 Success
$ cat /dev/hc_dev0             
1234
posted @ 2022-06-05 11:54  Mrlayfolk  阅读(240)  评论(0)    收藏  举报
回到顶部