驱动与用户层通信之ioctl的使用

1,125 阅读5分钟

驱动与用户层通信之ioctl的使用

一、 驱动中注册netdev

驱动中注册net_device_ops全局变量结构体:

static const struct net_device_ops myNetDeviceOptions = {
    .ndo_open               = ...,
    .ndo_stop               = ...,//等号左侧是net_device_ops结构体里的函数名
    .ndo_do_ioctl           = handle_ioctl,//等号右侧是驱动中对应的函数名
    .ndo_start_xmit         = ...,//利用这个结构体绑定了驱动中的函数
    ... ... ... ...
    //驱动需要使用的net device的相关函数需要注册,不使用的就无需列在这个结构体中
};

此外,还需将net_device_ops注册给上层:
在cfg80211初始化的时候,创建了网络设备,在这时候,驱动将这个ops函数集赋值给了驱动对应的网络设备。

struct net_device *devdev->netdev_ops = &myNetDeviceOptions ;

之后,用户层通过调用ioctl传到驱动层以后,就可以调用已注册的这个handle_ioctl函数来处理ioctl通信了。

二、 驱动中的处理ioctl通信的函数

handle_ioctl代码:

static int handle_ioctl(struct net_device *dev, struct ifreq *req, int command)
{
    int ret = 0;
    ///TODO: add ioctl command handler later
    switch(command)
    {
        case XXX://根据command值的不同,可以有很多条ioctl的通信通道
            printk("XXX\n");
            break;
       ... ... ... ...
        case SIOCDEVPRIVATE+2:
            printk("IOCTL \n");
            parse_ioctl_cmd(dev, req);//具体处理分析command的自己写的逻辑函数
	    break;
        default:
            ret = FAILEDERR;
    }
    return ret;
}
//其中定义如下:
//int parse_ioctl_cmd(struct net_device *dev, struct ifreq *ifr)

三、 内核中相关定义规范

这里用的是rk3399 android 10的代码。
关于SIOCDEVPRIVATE定义见:~/rk3399-android-10/kernel/include/uapi/linux/sockios.h

/* Device private ioctl calls */

/*
 *      These 16 ioctls are available to devices via the do_ioctl() device
 *      vector. Each device should include this file and redefine these names
 *      as their own. Because these are device dependent it is a good idea
 *      _NOT_ to issue them to random objects and hope.
 *
 *      THESE IOCTLS ARE _DEPRECATED_ AND WILL DISAPPEAR IN 2.5.X -DaveM
 */

#define SIOCDEVPRIVATE  0x89F0  /* to 89FF */
... ... ... ... 
#define SIOCGIFFLAGS    0x8913          /* get flags */

此处,每一个设备都可以定义自己的ioctl命令字,命令编号的范围是SIOCDEVPRIVATE到SIOCDEVPRIVATE + 15。具体是哪个编号不是定死的,只要驱动里与应用程序里的编号能对应上即可。

关于ifreq结构体定义见:~/rk3399-android-10/kernel/include/uapi/linux/if.h

#if __UAPI_DEF_IF_IFREQ
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;
};
#endif /* __UAPI_DEF_IF_IFREQ */

#define ifr_name        ifr_ifrn.ifrn_name      /* interface name       */
#define ifr_hwaddr      ifr_ifru.ifru_hwaddr    /* MAC address          */
#define ifr_addr        ifr_ifru.ifru_addr      /* address              */
#define ifr_dstaddr     ifr_ifru.ifru_dstaddr   /* other end of p-p lnk */
#define ifr_broadaddr   ifr_ifru.ifru_broadaddr /* broadcast address    */
#define ifr_netmask     ifr_ifru.ifru_netmask   /* interface net mask   */
#define ifr_flags       ifr_ifru.ifru_flags     /* flags                */
#define ifr_metric      ifr_ifru.ifru_ivalue    /* metric               */
#define ifr_mtu         ifr_ifru.ifru_mtu       /* mtu                  */
#define ifr_map         ifr_ifru.ifru_map       /* device map           */
#define ifr_slave       ifr_ifru.ifru_slave     /* slave device         */
#define ifr_data        ifr_ifru.ifru_data      /* for use by interface */
#define ifr_ifindex     ifr_ifru.ifru_ivalue    /* interface index      */
#define ifr_bandwidth   ifr_ifru.ifru_ivalue    /* link bandwidth       */
#define ifr_qlen        ifr_ifru.ifru_ivalue    /* Queue length         */
#define ifr_newname     ifr_ifru.ifru_newname   /* New name             */
#define ifr_settings    ifr_ifru.ifru_settings  /* Device/proto settings*/

由结构体定义+define结合来看,可知ioctl传进来的命令是存储在ifreq.ifr_data里的。

注:在parse_ioctl_cmd中可以用ifr->ifr_data来访问,要注意在驱动中想要把ifr->ifr_data中的数据操作,需要用到copy_from_user和copy_to_user。

四、 驱动中存取ifreq中数据

  1. 取出数据
struct my_command cmd_value;
 /*  my_command 是自定义的结构体,和用户层的对应上就可以。
 例如:
 typedef struct my_command{
	char *buf;
	int len;
    ... ... ... ... //其他你需要用到的参数
} my_command; 
*/

copy_from_user(&cmd_value, ifr->ifr_data, sizeof(my_command));//ifr是传进来的req
... ... ... ... 
char* cmd_str = NULL;
int buf_size = max(cmd_value.len ,MAX_SIZE);//buf_size是收到的包的大小
cmd_str = kmalloc((buf_size + 1), GFP_KERNEL);
copy_from_user(cmd_str, cmd_value.buf, cmd_value.len);
cmd_str[buf_size + 1] = '\0';
  1. 放入数据
//将需要返回给用户层的值存到cmd_str中。
copy_to_user(cmd_value.buf, cmd_str, 8);//即更新到ifr->ifr_data.buf里
//copy_to_user最后一个参数是传回的值的bytes个数
//这里cmd_str中填了两个int数,故而是8。
//将返回值填入传过来的ifr结构里后,
//在驱动处理ioctl通信的函数返回给上层后,
//上层会把这个更新了ifr_data.buf的ifr返回给用户空间

五、 用户层的软件代码:

  1. 主函数:
int main(int argc, char *argv[])
{
    ... ... ... ... 
    char* inf = "wlan0";//通过这个接口名称来建立通信线路,可通过ifconfig查看
	send_cmd_to_driver(inf, argc, argv);//这里面是运行本程序时的命令行参数
    ... ... ... ... 
	return 0;
}
  1. 发送指令+接收结果的处理函数
int send_cmd_to_driver(const char* inf, int argc, char *argv[])
{
	int sock;
	struct ifreq ifr;
	int ret = 0;
	char buf[MAX_SIZE];//MAX_SIZE是自定义的传送指令buffer最大的大小
	struct my_command command;//自定义的结构体
 
 /*  my_command 是自定义的结构体,和用户层的对应上就可以。
 例如:
 typedef struct my_command{
	char *buf;
	int len;
    ... ... ... ... //其他你需要用到的参数
} my_command; 
*/

	... ... ... ... 
//创建socket柄
	sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		printf("wrong sock!\n");
		return -1;
	}
 
   ... ... ... ... 
   
	memset(&ifr, 0, sizeof(ifr));//初始化req,准备发送
	strcpy(ifr.ifr_name, inf);//将网络接口名称填入req里
 
   //通信前先检查下接口的状态
	if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
		printf("%s Could not read interface %s flags: %s",__func__, inf, strerror(errno));
		return -1;
	}

	if (!(ifr.ifr_flags & IFF_UP)) {
		printf("%s is not up!\n",inf);
		return -1;
	}
 
   ........
  
	memset(&command, 0, sizeof(command));//初始化
	memset(buf, 0, sizeof(buf));

	........//一些对待传递的buf的赋值操作

	command.buf = buf;
	command.len= sizeof(buf);
	ifr.ifr_data = (void*)&command;//将待发送数据存入ifr中
 
   ... ... ... ...

	if ((ret = ioctl(sock, SIOCDEVPRIVATE+2, &ifr)) < 0) {//发给下层内核空间
		printf("%s: error ioctl[SIOCDEVPRIVATE+2] ret= %d\n", __func__, ret);
		return ret;
	}

	memcpy(&command, ifr.ifr_data, sizeof(struct my_command));
   //通信返回的结果可以通过command.buf中传递回来。由驱动赋好值后,传到用户层来读取。
   printf("i'm back: a= 0x%x   b=%d\n", *(unsigned int *)command.buf, *(unsigned int *)&command.buf[4]);
	//例如,这里这句输出意味着,buf中[0]-[3]这四个byte(也就是一个int的大小),存着第一个数值,用八进制输出。
   //buf中[4]-[7]这四个byte,存第二个数,用十进制输出。
   ... ... ... ...

	return ret;
}

总结

通过用户层ioctl的相关通信代码,无论这个用户软件是通过c编写并编译成可执行文件,还是通过native c++工程嵌入java代码工程中生成apk,均可成功地和内核中的驱动通信,这两种均已试验成功。