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驅動程式設計由平田豐著的這本書。

創作者介紹
創作者 csw.dawn 的頭像
csw.dawn

csw.dawn的部落格

csw.dawn 發表在 痞客邦 留言(0) 人氣()