【Linux】【驱动】ioctl介绍和应用场景

1.什么是ioctl

ioctl是用于设备(文件或者套接字)控制的公共接口,除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。简单数据传输外,大部分设备可以执行其他一些操作,比如,用户空间经常会请求设备状态,弹出设备,报告错误信息,改变波特率或者执行自破坏,等。这些操作通常通过ioctl方法执行,该方法实现了同名的系统调用。

2.ioctl

2.1 函数原型

在用户空间,ioctl系统调用原型如下:
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);

参数中使用的…表示可变数码的参数,但是在实际系统找中,系统调用不会真正使用可变数目的参数,而是必须具有精确定义的原型。

2.2 参数说明

  1. int d
    该参数是SOCK_DGRAM类型的套接字,一般初始化方式为:
    sock = socket(AF_INET, SOCK_DGRAM, 0);

  2. int request
    该参数是请求命令编号。这些编号一部分是已经编订好的,另外一部分是用户自己通过调用系统接口宏编制的。早期的Linux版本,为了方便程序员创建唯一的ioctl命令号,每一个命令号划分为多个位字段,Linux第一个版本使用一个16 bit的无符号整数。高8 bit是与设备相关的幻数,地8位是一个序列号嘛,在该设备内唯一。
    已经编码好的命令,可以参考文件kernel/Documentation/ioctl/ioctl-number.txt中的说明,下面列举出对私有设备的编码:
    0x89 00-06 arch/x86/include/asm/sockios.h
    0x89 0B-DF linux/sockios.h
    0x89 E0-EF linux/sockios.h SIOCPROTOPRIVATE range
    0x89 E0-EF linux/dn.h PROTOPRIVATE range
    0x89 F0-FF linux/sockios.h SIOCDEVPRIVATE range

    对于比较新的设备的请求命令编码,或者是用户自己添加的某类设备(比如 gpio)命令编码,则使用32 bit的请求码,这32位的编码有对应的规则:
    bits meaning
    31-30 00 - no parameters: uses _IO macro
    10 - read: _IOR
    01 - write: _IOW
    11 - read/write: _IOWR
    29-16 size of arguments
    15-8 ascii character supposedly,unique to each driver
    7-0 function #

    kernel中提供宏接口用于计算出唯一的命令请求码
    #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)))
    #define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
    #define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
    #define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

    常见的16bit的命令编码在文件kernel\include\linux\socketios.h中定义:
    /* Socket configuration controls. */
    #define SIOCGIFNAME 0x8910 /* get iface name*/
    #define SIOCSIFLINK 0x8911 /* set iface channel */
    #define SIOCGIFCONF 0x8912 /* get iface list */
    #define SIOCGIFFLAGS 0x8913 /* get flags */
    #define SIOCSIFFLAGS 0x8914 /* set flags */
    #define SIOCGIFADDR 0x8915 /* get PA address */
    #define SIOCSIFADDR 0x8916 /* set PA address */
    `

  3. 可变参数
    通常使用到的可变参数类型如下:
    多个设备配置信息
    struct ifconf ifconf_st; /用于保存多个配置/
    struct ifconf {
    int ifc_len; /* size of buffer */
    union {
    char __user *ifcu_buf;
    struct ifreq __user *ifcu_req;
    } ifc_ifcu;
    };

    某个接口的配置信息
    struct ifreq ifr;

    struct ifreq {
    #define IFHWADDRLEN 6
    union
    {
    char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
    } ifr_ifrn;

    union {
    struct sockaddr ifru_addr;
    struct sockaddr ifru_dstaddr;
    struct sockaddr ifru_broadaddr;
    struct sockaddr ifru_netmask;
    struct sockaddr ifru_hwaddr;
    short ifru_flags;
    int ifru_ivalue;
    int ifru_mtu;
    struct ifmap ifru_map;
    char ifru_slave[IFNAMSIZ]; /* Just fits the size */
    char ifru_newname[IFNAMSIZ];
    void __user * ifru_data;
    struct if_settings ifru_settings;
    } ifr_ifru;
    };

    无线设备接口信息:
    struct iwreq iwr;
    struct iwreq
    {
    union {
    char ifrn_name[IFNAMSIZ]; /* if name, e.g. "eth0" */
    } ifr_ifrn;

    /* Data part (defined just above) */
    union iwreq_data u;
    };

/* ------------------------ IOCTL REQUEST ------------------------ */
/*
* This structure defines the payload of an ioctl, and is used
* below.
*
* Note that this structure should fit on the memory footprint
* of iwreq (which is the same as ifreq), which mean a max size of
* 16 octets = 128 bits. Warning, pointers might be 64 bits wide...
* You should check this when increasing the structures defined
* above in this file...
*/

`
union iwreq_data
{
/* Config - generic */
char name[IFNAMSIZ];
/* Name : used to verify the presence of wireless extensions.
* Name of the protocol/provider… */

struct iw_point essid;      /* Extended network name */
struct iw_param nwid;       /* network id (or domain - the cell) */
struct iw_freq  freq;       /* frequency or channel :
                 * 0-1000 = channel
                 * > 1000 = frequency in Hz */

struct iw_param sens;       /* signal level threshold */
struct iw_param bitrate;    /* default bit rate */
struct iw_param txpower;    /* default transmit power */
struct iw_param rts;        /* RTS threshold threshold */
struct iw_param frag;       /* Fragmentation threshold */
__u32       mode;       /* Operation mode */
struct iw_param retry;      /* Retry limits & lifetime */

struct iw_point encoding;   /* Encoding stuff : tokens */
struct iw_param power;      /* PM duration/timeout */
struct iw_quality qual;     /* Quality part of statistics */

struct sockaddr ap_addr;    /* Access point address */
struct sockaddr addr;       /* Destination address (hw/mac) */

struct iw_param param;      /* Other small parameters */
struct iw_point data;       /* Other large parameters */`

};
/*
* For all data larger than 16 octets, we need to use a
* pointer to memory allocated in user space.
*/
struct iw_point
{
void /*__user*/ *pointer; /* Pointer to the data (in user space) */
__u16 length; /* number of fields or size in bytes */
__u16 flags; /* Optional params */
};

上面的参数中,最常用的是:
iwr.u.data.flags = FLAG;
iwr.u.data.pointer = (void*)(buffer);
iwr.u.data.length = sizeof(buf);
一定要注意传入buffer的长度一定要大于等于和驱动中cop_to_user的长度,否则出现段错误。
时间戳
struct timeval udp_arrival;
if (ioctl(usd, SIOCGSTAMP, &udp_arrival) < 0)

3.sock_ioctl函数
该函数中文件kernel\net\socket.c中定义:
static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)
{
struct socket *sock;
struct sock *sk;
void __user *argp = (void __user *)arg;
int pid, err;
struct net *net;

sock = file->private_data;
sk = sock->sk;
net = sock_net(sk);
printk("%s %d cmd = 0x%x\r\n", __FUNCTION__, __LINE__, cmd);
printk("%s %d SIOCDEVPRIVATE = 0x%x, SIOCIWFIRST = 0x%x, SIOCIWLAST = 0x%x\r\n", __FUNCTION__, __LINE__, SIOCDEVPRIVATE, SIOCIWFIRST, SIOCIWLAST);
if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) { /*SIOCDEVPRIVATE=0x8BE0*/
    err = dev_ioctl(net, cmd, argp);/*查找设备的ioctl函数*/
} else

#ifdef CONFIG_WEXT_CORE
if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) { /*SIOCIWFIRST=0x8B00, SIOCIWLAST=0x8BFF*/
err = dev_ioctl(net, cmd, argp); /*查找设备的ioctl函数*/
} else

#endif
switch (cmd) {
case FIOSETOWN:
case SIOCSPGRP:
err = -EFAULT;
if (get_user(pid, (int __user *)argp))
break;
err = f_setown(sock->file, pid, 1);
break;
case FIOGETOWN:
case SIOCGPGRP:
err = put_user(f_getown(sock->file),
(int __user *)argp);
break;
case SIOCGIFBR:
case SIOCSIFBR:
case SIOCBRADDBR:
case SIOCBRDELBR:
err = -ENOPKG;
if (!br_ioctl_hook)
request_module("bridge");
mutex_lock(&br_ioctl_mutex);
if (br_ioctl_hook)
err = br_ioctl_hook(net, cmd, argp);
mutex_unlock(&br_ioctl_mutex);
break;
case SIOCGIFVLAN:
case SIOCSIFVLAN:
err = -ENOPKG;
if (!vlan_ioctl_hook)
request_module("8021q");
mutex_lock(&vlan_ioctl_mutex);
if (vlan_ioctl_hook)
err = vlan_ioctl_hook(net, argp);
mutex_unlock(&vlan_ioctl_mutex);
break;
case SIOCADDDLCI:
case SIOCDELDLCI:
err = -ENOPKG;
if (!dlci_ioctl_hook)
request_module("dlci");
mutex_lock(&dlci_ioctl_mutex);
if (dlci_ioctl_hook)
err = dlci_ioctl_hook(cmd, argp);
mutex_unlock(&dlci_ioctl_mutex);
break;
default:
err = sock_do_ioctl(net, sock, cmd, arg);/*调用sock的ioctl函数 sock->ops->ioctl(sock, cmd, arg)*/
break;
}
return err;
}

如果ioctl的命令不在SIOCIWFIRST 和 SIOCIWLAST范围内,则匹配其他命令,如果匹配不上,则进入
default:
err = sock_do_ioctl(net, sock, cmd, arg);
printk ("%s %d err = 0x%x\r\n", __FUNCTION__, __LINE__, err);
break;

调用sock的ioctl函数,而不是设备的ioctl函数。 如果ioctl命令编码不在范围内,那么返回错误:
sock_ioctl 953 cmd = 0x8C02
sock_ioctl 954 SIOCDEVPRIVATE = 0x89f0, SIOCIWFIRST = 0x8b00, SIOCIWLAST = 0x8bff
ioctl error:: Operation not supported

添加命令

根据功能需求,有时候需要在现有的命令上添加新ioctl功能接口,根据上面的描述,有两种情况
第一:在现有的命令编码上添加新的接口,比如在无线驱动中要添加一些新的命令,考虑到预留的编码范围:
#define SIOCIWFIRSTPRIV 0x8BE0
#define SIOCIWLASTPRIV 0x8BFF
如果每一条命令都是用一个编码,很容易用完,所以,一般在实现时,使用2级编码的方法进行添加。
比如定义#define MY_PRIV_IOCTL (SIOCIWFIRSTPRIV + 0x01)
用于添加私有的ioctl命令,那么可以在此基础上添加subcmd,作为多个命令的区分。有两种方法:
一是通过请求参数中的成员,比如iwr.u.data.flags = MY_DRIVER_GET_STATION_LIST_INFO;
二是通过参数iwr.u.data.pointer指向的数据结构变量中定义subcmd进行区分不同的命令;

常用例子

  1. 获取接口的MAC地址:
    struct ifreq ifr;
    struct ethhdr* ethh = (struct ethhdr*)pkt;
    memset (&device, 0, sizeof (device));
    if ((device.sll_ifindex = if_nametoindex (interface)) == 0) {
    return -1;
    }
    if ((fd = socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL))) < 0) {
    return -1;
    }
    memset (&ifr, 0, sizeof (ifr));
    snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "%s", interface);
    if (ioctl (fd, SIOCGIFHWADDR, &ifr) < 0) {
    close(fd);
    return (EXIT_FAILURE);
    }
    close (fd);
    memcpy (ethh->h_source, ifr.ifr_hwaddr.sa_data, 6);

  2. 获取网络设备序号
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /*获取网络设备ifname的序号*/
    if (-1 == ioctl(sockfd, SIOCGIFINDEX, &ifr))
    {
    if (errbuf != NULL)
    {
    perror("ioctl error");
    }
    return -1;
    }
    return ifr.ifr_ifindex;

  3. 获取和设置设备的flags
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
    if (-1 == ioctl(sockfd, SIOCGIFFLAGS, &ifr))
    {
    perror("ioctl error");
    return -1;
    }

    _setbits(ifr.ifr_flags, (IFF_UP | IFF_BROADCAST | IFF_MULTICAST));
    _clrbits(ifr.ifr_flags, (IFF_ALLMULTI | IFF_PROMISC));
    if (-1 == ioctl(sockfd, SIOCSIFFLAGS, &ifr))
    {
    perror(“ioctl error”);
    return -1;
    }
    return 0;

  4. 读取某个GPIO设备
    #define HARDWARE_GPIO_IOCTL_BASE 0x01
    #define HARDWARE_GPIO_IOCTL_CMD1 HARDWARE0_GPIO_IOCTL_BASE
    #define HARDWARE_GPIO_MAGIC 0xB2
    #define HARDWARE_GPIO_BTN_READ_IOR(HARDWARE_GPIO_MAGIC,HARDWARE_GPIO_IOCTL_CMD1, int)
    int fd = open("/dev/hardware_gpio_chrdev", O_RDONLY | O_CREAT);
    int val = 0;
    rt = ioctl(fd, HARDWARE_GPIO_BTN_READ, &val); /*读取某个gpio的状态*/

  5. 设置非阻塞套接字
    int sock_set_blocking(const sock_t sock)
    {
    int rc;
    rc = fcntl(sock, F_GETFL, NULL);
    if (rc >= 0)
    {
    rc = fcntl(sock, F_SETFL, rc & (~O_NONBLOCK));
    }
    return rc;
    }

猜你喜欢

转载自blog.csdn.net/vickytong1018/article/details/77834272
今日推荐