2012年1月3日 星期二

基礎 Linux Device Driver 驅動程式#9 (IOCTL)

IOCTL 是一種系統呼叫介面,user process 呼叫 ioctl() 即可對驅動程式送出系統呼叫,
如此會呼叫驅動程式的 IOCTL 處理函式,也可跟驅動程式交換資料。
交換資料的格式,可由驅動程式開發者自由決定。

IOCTL 方法的 prototype如下:
int (*ioctl)(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);

inode:  是開啟裝置檔的相關資訊。
filp:   可取出驅動程式的私有資料。
cmd:    是 IOCTL 的指令,且不可省略,驅動程式可由它得知 user process 想做什麼。
arg:    是 ioctl()可變引數(...)的參數,內含 user process的指標,但驅動程式不得
        直接讀寫這個指標,必須透過copy_from_user() 及 copy_to_user() 讀寫資料。
     
但,各位可能要注意一個小地方囉,我目前用的 linux kernel 是 2.6.38,
ioctl 這個 handler 在 2.3.36的核心版本已不用這個名稱了,
改用以下兩個:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl)   (struct file *, unsigned int, unsigned long);

所以要注意喔,如果各位也是和我用一樣新核心的話,就....你知道的。
我示範的 code 也會用 unlocked_ioctl 這個 handler。

IOCTL 指令的格式是以巨集定義的。驅動程式與user process 的程式共用一個標頭檔。
四個巨集分別如下:
_IO():    無引數的 IOCTL。
_IOR():   從驅動程式讀取資料。
_IOW():   把資料寫給驅動程式。
_IOWR():  與驅動程式讀寫資料。

定義在 include/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)))

驅動程式可使用 _IO_SIZE 巨集得知傳給 ioctl() 可變引數的結構大小。
也是定義在 include/asm-generic/ioctl.h
#define _IOC_SIZE(nr)           (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

在讀寫 user space 的指標時,可以呼叫 access_ok() 判斷指標可否讀讀寫。
定義在 include/asm-generic/uaccess.h
#define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))

當然有些時候,會只限定root才能使用 ioctl,一般使用者不能使用,可用 int capable(int cap);

再來寫個 code 跑一下吧^^
但在這之前,我們先看一下我們要 include 的標頭檔,如下:

test_ioctl.h
/*****************************************************************************/

#ifndef _IOCTL_TEST_H
#define _IOCTL_TEST_H

#include <linux/ioctl.h>

struct ioctl_arg {
        unsigned int reg;
        unsigned int val;
};

/* 這裡要找一個沒用到的號碼,請參考 Documentation/ioctl/ioctl-number.txt */
#define IOC_MAGIC '\x66'

/* 您要的動作 */
#define IOCTL_VALSET      _IOW(IOC_MAGIC, 0, struct ioctl_arg)
#define IOCTL_VALGET      _IOR(IOC_MAGIC, 1, struct ioctl_arg)
#define IOCTL_VALGET_NUM  _IOR(IOC_MAGIC, 2, int)
#define IOCTL_VALSET_NUM  _IOW(IOC_MAGIC, 3, int)

#define IOCTL_VAL_MAXNR 3

#endif

/*****************************************************************************/

test_ioctl.c 原始碼如下:
/*****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "test_ioctl.h"

#define DRIVER_NAME "test_ioctl"
static unsigned int test_ioctl_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_ioctl_cdev;
static int ioctl_num = 0;

struct test_ioctl_data {
unsigned char val;
rwlock_t lock;
};

static long test_ioctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
int retval;
unsigned char val;
struct ioctl_arg data;

memset(&data, 0, sizeof(data));
switch (cmd) {
case IOCTL_VALSET:
/*
if (!capable(CAP_SYS_ADMIN)) {
retval = -EPERM;
goto done;
}
if (!access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd))) {
retval = -EFAULT;
goto done;
}
*/
if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
retval = -EFAULT;
goto done;
}
printk(KERN_ALERT "IOCTL set val:%x .\n", data.val);

write_lock(&ioctl_data->lock);
                        ioctl_data->val = data.val;
                        write_unlock(&ioctl_data->lock);

break;
case IOCTL_VALGET:
/*
if (!access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))) {
                                retval = -EFAULT;
                                goto done;
                        }
*/
read_lock(&ioctl_data->lock);
                        val = ioctl_data->val;
                        read_unlock(&ioctl_data->lock);
                        data.val = val;

if (copy_to_user((int __user *)arg, &data, sizeof(data)) ) {
                                retval = -EFAULT;
                                goto done;
                        }

break;

case IOCTL_VALGET_NUM:
retval = __put_user(ioctl_num, (int __user *)arg);
break;
case IOCTL_VALSET_NUM:
/*
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
*/
ioctl_num = arg;

break;
default:
retval = -ENOTTY;
}

done:
return retval;
}

ssize_t test_ioctl_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
unsigned char val;
int retval;
int i = 0;

read_lock(&ioctl_data->lock);
val = ioctl_data->val;
read_unlock(&ioctl_data->lock);

for (;i < count ;i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}

retval = count;

out:
return retval;
}

static int test_ioctl_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "%s call.\n", __func__);
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}

return 0;
}

static int test_ioctl_open(struct inode *inode, struct file *filp)
{
struct test_ioctl_data *ioctl_data;
printk(KERN_ALERT "%s call.\n", __func__);

ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL);
if (ioctl_data == NULL)
return -ENOMEM;

rwlock_init(&ioctl_data->lock);
ioctl_data->val = 0xFF;

filp->private_data = ioctl_data;

return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_ioctl_open,
.release = test_ioctl_close,
.read = test_ioctl_read,
.unlocked_ioctl = test_ioctl_ioctl,
};

static int test_ioctl_init(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;

test_ioctl_major = MAJOR(dev);

cdev_init(&test_ioctl_cdev, &fops);
cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;

printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_ioctl_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_ioctl_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);

return -1;
}

static void test_ioctl_exit(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);

cdev_del(&test_ioctl_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}

module_init(test_ioctl_init);
module_exit(test_ioctl_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_ioctl module.");

/*****************************************************************************/

那當然還少不了測試程式囉。

test.c 如下:
/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "../test_ioctl.h"

#define DEVFILE "/dev/t1"

int main(void)
{
struct ioctl_arg cmd;
        int fd;
long ret;
int num = 0;

        fd = open(DEVFILE, O_RDWR);
        if (fd == -1)
                perror("open");

memset(&cmd, 0, sizeof(cmd));
        ret = ioctl(fd, IOCTL_VALGET, &cmd);

if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }
        printf("val %x\n", cmd.val);

        cmd.val = 0xCC;
        ret = ioctl(fd, IOCTL_VALSET, &cmd);
        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }

ret = ioctl(fd, IOCTL_VALGET, &cmd);

        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }
        printf("val %x\n", cmd.val);


num = 100;
ret = ioctl(fd, IOCTL_VALSET_NUM, num);
        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }

ret = ioctl(fd, IOCTL_VALGET_NUM, &num);

        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }
        printf("num %d\n", num);

        if (close(fd) != 0)
                perror("close");

        return 0;
}

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := test_ioctl.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

開始測試囉^^
# ls
Makefile  test_code  test_ioctl.c  test_ioctl.h
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/test_ioctl/test_ioctl.o
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c: In function ‘test_ioctl_ioctl’:
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c:23:6: warning: ‘retval’ may be used uninitialized in this function
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/test_ioctl/test_ioctl.mod.o
  LD [M]  /opt/test_driver/my_driver/test_ioctl/test_ioctl.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_ioctl.ko
test_ioctl driver(major: 248) installed.
# cd test_code/
# gcc test.c -o test
# cd ..
# mknod /dev/t1 c 248 0
# ./test_code/test
val ff
val cc
num 100
# dmesg | tail
... /* 這裡略過 */
test_ioctl_open call.
IOCTL set val:cc .
test_ioctl_close call.
# rm /dev/t1
# rmmod test_ioctl

# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CLEAN   /opt/test_driver/my_driver/test_ioctl/.tmp_versions
  CLEAN   /opt/test_driver/my_driver/test_ioctl/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'


完成 ^^


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

11 則留言:

  1. hi, 你好

    我也是讀平田豐這本書的, 關於這個章節我想請教你一些問題, 謝謝.

    IOCTL 方法的原型是
    int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

    在你的範例程式裏面宣告為
    static long test_ioctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

    是可以直接省略struct inode *inode的意思嗎?

    因為我在書中, 或是網路上其他人的文章中都沒有看到這樣的寫法.

    回覆刪除
  2. 又, 在平田豐書中(中文版第三版)的範例程式裡, 的ioctl是宣告為(P.155)
    int devone_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
    有四個參數

    可是他的user space app呼叫(P.160)的時候只使用
    ioctl(fd, IOCTL_VALGET, &cmd);
    為什麼只有三個參數呢?

    謝謝

    回覆刪除
  3. 你好, 我看懂你的範例了, 因為你是用新的unlocked_ioctl, 所以 static long test_ioctl_ioctl()才會只有三個參數. 我了解了.

    那請問你知道我的第二個問題嗎?

    回覆刪除
  4. 您好:
    首先,要先說聲抱歉,我平常很忙,並不是能隨時注意部落格的動向,這點請您見諒。
    關於那部份的話,我從unistd.h 找到了以下這段:

    ..... /* 略過 */

    /* fs/ioctl.c */ <----- 這裡
    #define __NR_ioctl 29
    __SYSCALL(__NR_ioctl, sys_ioctl)

    ..... /* 略過 */

    再去看 fs/ioctl.c,片段如下:
    /* 以上略過 */
    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);
    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;
    }

    這是您要的嗎?

    回覆刪除
  5. 資訊科技這部份本來就是互相交流,
    我並不是高手,也是一直在研究這部份,
    只是最近工作較忙,沒時間可碰這塊,
    加油囉,共勉之。

    回覆刪除
  6. 如果是user space的部份,請參考
    man 2 ioctl
    IOCTL(2) Linux Programmer's Manual IOCTL(2)

    NAME
    ioctl - control device

    SYNOPSIS
    #include

    int ioctl(int d, int request, ...);

    DESCRIPTION
    The ioctl() function manipulates the underlying device parameters of
    special files. In particular, many operating characteristics of char‐
    acter special files (e.g., terminals) may be controlled with ioctl()
    requests. The argument d must be an open file descriptor.

    The second argument is a device-dependent request code. The third
    argument is an untyped pointer to memory. It's traditionally char
    *argp (from the days before void * was valid C), and will be so named
    for this discussion.

    /* 以下略過 */

    回覆刪除
  7. hi 你好

    很感謝你的撥空回答, 我又花了一個早上研究了一下這個部分

    我的一些心得如下, 這邊以舊的ioctl為主(2.6.35以前), 以及平田豐的例子

    其實我本來最主要的問題是(不知道會不會很蠢)

    *為什麼user space 呼叫ioctl的時候只有帶入三個參數?*

    EX: ioctl(fd, IOCTL_VALGET, &cmd);(P.160)

    但是我看ioctl的原型是四個參數

    EX: int devone_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)(P.155)

    這點我一直沒有辦法突破~

    回覆刪除
  8. 我又仔細的看了幾遍書, 以及根據你的提示去追原始碼

    我想user space 的 ioctl()(P.160)與

    file_operations中的ioctl()方法, 並不是同一個函式(這就是我的盲點), 所以從來就沒有三個或四個參數的問題

    user space呼叫 ioctl()之後的流程/fs/ioctl.c中

    SYSCALL_DEFINE3()定義了(雖然我還看不懂是怎麼定義的)

    user space中呼叫了ioctl()之後應該要做哪一些處理, 以及ioctl()的"三"個參數

    然後SYSCALL_DEFINE3()裡面呼叫do_vfs_ioctl(),

    do_vfs_ioctl()再呼叫vfs_ioctl()

    最後vfs_ioctl()裡面會呼叫file_operations中的ioctl()(2.6.36以後是呼叫unlock_ioctl()),

    在這邊才是送"四"個參數給file_operations中的ioctl()

    然後就做你的驅動程式裡面的.ioctl = your_ioctl

    回覆刪除
  9. 我好像看懂SYSCALL_DEFINE3()的定義了

    SYSCALL_DEFINE3()透過一些#define的作用之後

    其實SYSCALL_DEFINE3(ioctl,....)這個函式就是

    sys_ioctl()這個system call對不對?

    可是這樣我看不出哪裡呼叫了sys_ioctl()

    ioctl驅動程式最後應該都一定要呼叫sys_ioctl()對嗎?

    在這個範例裡面有呼叫嗎?

    謝謝

    回覆刪除
  10. 我大概知道了,

    sys_ioctl()應該是透過unistd.h的這段定義

    /* fs/ioctl.c */
    #define __NR_ioctl 29
    __SYSCALL(__NR_ioctl, sys_ioctl)

    來與user space的ioctl()定義在一起的吧?

    所以在user space 呼叫ioctl()事實上就是呼叫了ioctl.c裡面的sys_ioctl(), 在ioctl.c的程式碼中就是SYSCALL_DEFINE3()這段.

    以上是我的理解.

    回覆刪除