天天看點

Linux驅動--ioctl

ioctl系統框圖

Linux驅動--ioctl

使用者空間的ioctl

#include <sys/ioctl.h> 
 
int ioctl(int fd, int cmd, ...) ;
           
參數 描述
fd 檔案描述符
cmd 互動協定,裝置驅動将根據cmd執行對應操作
可變參數arg,依賴cmd指定長度以及類型

ioctl()執行成功時傳回0,失敗則傳回-1并設定全局變量errorno值,如下: 

EBADF d is not a valid descriptor. 
EFAULT argp references an inaccessible memory area. 
EINVAL Request or argp is not valid. 
ENOTTY d is not associated with a character special device. 
ENOTTY The specified request does not apply to the kind of object that the descriptor d references.
           

是以,在使用者空間使用ioctl時,可以做如下的出錯判斷以及處理:

int ret;
    ret = ioctl(fd, MYCMD);
    if (ret == -1) {
        printf("ioctl: %s\n", strerror(errno));
    }
           

tips: 在實際應用中,ioctl出錯時的errorno大部分是ENOTTY(error not a typewriter),顧名思義,即第一個參數fd指向的不是一個字元裝置,不支援ioctl操作,這時候應該檢查前面的open函數是否出錯或者裝置路徑是否正确。

驅動中的ioctl()

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
           

在新版核心中,unlocked_ioctl()與compat_ioctl()取代了ioctl()。unlocked_ioctl(),顧名思義,應該在無大核心鎖(BKL)的情況下調用;compat_ioctl(),compat全稱compatible(相容的),主要目的是為64位系統提供32位ioctl的相容方法,也是在無大核心鎖的情況下調用。

tips:在字元裝置驅動開發中,一般情況下隻要實作unlocked_ioctl()即可,因為在vfs層的代碼是直接調用unlocked_ioctl()。

// fs/ioctl.c
 
static long vfs_ioctl(struct file *filp, unsigned int cmd,
              unsigned long arg)
{
    int error = -ENOTTY;
 
    if (!filp->f_op || !filp->f_op->unlocked_ioctl)           
        goto out;
 
    error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
    if (error == -ENOIOCTLCMD) {
        error = -ENOTTY;
    }   
 out:
    return error;
}
           

ioctl指令,使用者與驅動之間的協定

在linux中,提供了一種ioctl指令的統一格式,将32位int型資料劃分為四個位段,如下圖所示: 

Linux驅動--ioctl

在核心中,提供了宏接口以生成上述格式的ioctl指令:

// include/uapi/asm-generic/ioctl.h
 
#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \
     ((type) << _IOC_TYPESHIFT) | \
     ((nr)   << _IOC_NRSHIFT) | \
     ((size) << _IOC_SIZESHIFT))
           
  1. dir(direction),ioctl指令通路模式(資料傳輸方向),占據2bit,可以為_IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别訓示了四種通路模式:無資料、讀資料、寫資料、讀寫資料;
  2. type(device type),裝置類型,占據8bit,在一些文獻中翻譯為“幻數”或者“魔數”,可以為任意char型字元,例如‘a’、‘b’、‘c’等等,其主要作用是使ioctl指令有唯一的裝置辨別; 

    tips:Documentions/ioctl-number.txt記錄了在核心中已經使用的“魔數”字元,為避免沖突,在自定義ioctl指令之前應該先查閱該文檔。

  3. nr(number),指令編号/序數,占據8bit,可以為任意unsigned char型資料,取值範圍0~255,如果定義了多個ioctl指令,通常從0開始編号遞增;
  4. size,涉及到ioctl第三個參數arg,占據13bit或者14bit(體系相關,arm架構一般為14位),指定了arg的資料類型及長度,如果在驅動的ioctl實作中不檢查,通常可以忽略該參數。
_IO 定義不帶參數的ioctl指令
_IOW 定義帶寫參數的ioctl指令(copy_from_user)
_IOR 定義帶讀參數的ioctl指令(copy_to_user)
_IOWR 定義帶讀寫參數的ioctl指令

ioctl-test,執行個體分析

 本例假設一個帶寄存器的裝置,設計了一個ioctl接口實作裝置初始化、讀寫寄存器等功能。在本例中,為了攜帶更多的資料,ioctl的第三個可變參數為指針類型,指向自定義的結構體struct msg。

1、ioctl-test.h,使用者空間和核心空間共用的頭檔案,包含ioctl指令及相關宏定義,可以了解為一份“協定”檔案,代碼如下:

// ioctl-test.h
 
#ifndef __IOCTL_TEST_H__
#define __IOCTL_TEST_H__
 
#include <linux/ioctl.h>    // 核心空間
// #include <sys/ioctl.h>   // 使用者空間
 
/* 定義裝置類型 */
#define IOC_MAGIC  'c'
 
/* 初始化裝置 */
#define IOCINIT    _IO(IOC_MAGIC, 0)
 
/* 讀寄存器 */
#define IOCGREG    _IOW(IOC_MAGIC, 1, int)
 
/* 寫寄存器 */
#define IOCWREG    _IOR(IOC_MAGIC, 2, int)
 
#define IOC_MAXNR  3
 
struct msg {
    int addr;
    unsigned int data;
};
 
#endif
           

2、ioctl-test-driver.c,字元裝置驅動,實作了unlocked_ioctl接口,根據上層使用者的cmd執行對應的操作(初始化裝置、讀寄存器、寫寄存器)。在接收上層cmd之前應該對其進行充分的檢查,流程及具體代碼實作如下:

// ioctl-test-driver.c
......
 
static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = test_open,
    .release = test_close,
    .read = test_read,
    .write = etst_write,
    .unlocked_ioctl = test_ioctl,
};
 
......
 
static long test_ioctl(struct file *file, unsigned int cmd, \
                        unsigned long arg)
{
    //printk("[%s]\n", __func__);
 
    int ret;
    struct msg my_msg;
 
    /* 檢查裝置類型 */
    if (_IOC_TYPE(cmd) != IOC_MAGIC) {
        pr_err("[%s] command type [%c] error!\n", \
            __func__, _IOC_TYPE(cmd));
        return -ENOTTY; 
    }
 
    /* 檢查序數 */
    if (_IOC_NR(cmd) > IOC_MAXNR) { 
        pr_err("[%s] command numer [%d] exceeded!\n", 
            __func__, _IOC_NR(cmd));
        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)
        return -EFAULT;
 
    switch(cmd) {
    /* 初始化裝置 */
    case IOCINIT:
        init();
        break;
 
    /* 讀寄存器 */
    case IOCGREG:
        ret = copy_from_user(&msg, \
            (struct msg __user *)arg, sizeof(my_msg));
        if (ret) 
            return -EFAULT;
        msg->data = read_reg(msg->addr);
        ret = copy_to_user((struct msg __user *)arg, \
                &msg, sizeof(my_msg));
        if (ret) 
            return -EFAULT;
        break;
 
    /* 寫寄存器 */
    case IOCWREG:
        ret = copy_from_user(&msg, \
            (struct msg __user *)arg, sizeof(my_msg));
        if (ret) 
            return -EFAULT;
        write_reg(msg->addr, msg->data);
        break;
 
    default:
        return -ENOTTY;
    }
 
    return 0;
}
           

3、ioctl-test.c,運作在使用者空間的測試程式:

// ioctl-test.c
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h> 
 
#include "ioctl-test.h"
 
int main(int argc, char **argv)
{
 
    int fd;
    int ret;
    struct msg my_msg;
 
    fd = open("/dev/ioctl-test", O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(-2);
    }
 
    /* 初始化裝置 */
    ret = ioctl(fd, IOCINIT);
    if (ret) {
        perror("ioctl init:");
        exit(-3);
    }
 
    /* 往寄存器0x01寫入資料0xef */
    memset(&my_msg, 0, sizeof(my_msg));
    my_msg.addr = 0x01;
    my_msg.data = 0xef;
    ret = ioctl(fd, IOCWREG, &my_msg);
    if (ret) {
        perror("ioctl read:");
        exit(-4);
    }
 
    /* 讀寄存器0x01 */
    memset(&my_msg, 0, sizeof(my_msg));
    my_msg.addr = 0x01;
    ret = ioctl(fd, IOCGREG, &my_msg);
    if (ret) {
        perror("ioctl write");
        exit(-5);
    }
    printf("read: %#x\n", my_msg.data);
 
    return 0;
}
           

轉載于:https://blog.csdn.net/renlonggg/article/details/80567073

繼續閱讀