# 问题

最近在 ubuntu16.04 上调 MTK 的 WiFi 驱动,发现 iwpriv 命令不能正常使用,现象如下:

wifi_group@ubuntu16:~/liyongjun/MT7615_onPC_Lq$ iwpriv rao
rao       no private ioctls.

修改驱动,使 CONFIG_APSTA_MIXED_SUPPORT 宏生效,iwpriv 能够列出功能参数,但是具体功能仍然无法正常使用,现象如下:

wifi_group@ubuntu16:~/liyongjun/MT7615_onPC_Lq$ iwpriv ra0
ra0       Available private ioctls :
          set              (8BE2) : set 1536 char  & get   0
          show             (8BF1) : set 1024 char  & get   0
          get_site_survey  (8BED) : set 1024 char  & get 1024 char
          set_wsc_oob      (8BF9) : set 1024 char  & get 1024 char
          get_mac_table    (8BEF) : set 1024 char  & get 1024 char
          e2p              (8BE7) : set 1024 char  & get 1024 char
          bbp              (8BE3) : set 1024 char  & get 1024 char
          mac              (8BE5) : set 1024 char  & get 1024 char
          rf               (8BF3) : set 1024 char  & get 1024 char
          get_wsc_profile  (8BF2) : set 1024 char  & get 1024 char
          get_ba_table     (8BF6) : set 1024 char  & get 1024 char
          stat             (8BE9) : set 1024 char  & get 1024 char
          rx               (8BFB) : set 1024 char  & get 1024 char

wifi_group@ubuntu16:~/liyongjun/MT7615_onPC_Lq$ iwpriv ra0 set
Interface doesn't accept private ioctl...
set (8BE2): Operation not permitted
wifi_group@ubuntu16:~/liyongjun/MT7615_onPC_Lq$ sudo iwpriv ra0 set
Interface doesn't accept private ioctl...
set (8BE2): Operation not supported

# 追寻

同样的驱动在 ubuntu14.04 中可以正常使用 iwpriv,由此,怀疑是内核版本问题。

ubuntu14.04 内核版本是 4.4.0

ubuntu16.04 内核版本是 4.15.0

比较两份内核代码的差异,只需比较 net/ 部分即可

并通过函数调用追溯

发现 net/wireless/wext-core.c 中,函数 wireless_process_ioctl 在 4.4.0 kernel 中为:

/*
 * Main IOCTl dispatcher.
 * Check the type of IOCTL and call the appropriate wrapper...
 */
static int wireless_process_ioctl(struct net *net, struct ifreq *ifr,
				  unsigned int cmd,
				  struct iw_request_info *info,
				  wext_ioctl_func standard,
				  wext_ioctl_func private)
{
	struct iwreq *iwr = (struct iwreq *) ifr;
	struct net_device *dev;
	iw_handler	handler;

	/* Permissions are already checked in dev_ioctl() before calling us.
	 * The copy_to/from_user() of ifr is also dealt with in there */

	/* Make sure the device exist */
	if ((dev = __dev_get_by_name(net, ifr->ifr_name)) == NULL)
		return -ENODEV;

	/* A bunch of special cases, then the generic case...
	 * Note that 'cmd' is already filtered in dev_ioctl() with
	 * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */
	if (cmd == SIOCGIWSTATS)
		return standard(dev, iwr, cmd, info,
				&iw_handler_get_iwstats);

#ifdef CONFIG_WEXT_PRIV
	if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
		return standard(dev, iwr, cmd, info,
				iw_handler_get_private);
#endif

	/* Basic check */
	if (!netif_device_present(dev))
		return -ENODEV;

	/* New driver API : try to find the handler */
	handler = get_handler(dev, cmd);
	if (handler) {
		/* Standard and private are not the same */
		if (cmd < SIOCIWFIRSTPRIV)
			return standard(dev, iwr, cmd, info, handler);
		else if (private)
			return private(dev, iwr, cmd, info, handler);
	}
	/* Old driver API : call driver ioctl handler */
	if (dev->netdev_ops->ndo_do_ioctl)
		return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd);
	return -EOPNOTSUPP;
}

在 4.15.0 kernel 中为:

/*
 * Main IOCTl dispatcher.
 * Check the type of IOCTL and call the appropriate wrapper...
 */
static int wireless_process_ioctl(struct net *net, struct iwreq *iwr,
                                  unsigned int cmd,
                                  struct iw_request_info *info,
                                  wext_ioctl_func standard,
                                  wext_ioctl_func private)
{
        struct net_device *dev;
        iw_handler      handler;

        /* Permissions are already checked in dev_ioctl() before calling us.
         * The copy_to/from_user() of ifr is also dealt with in there */

        /* Make sure the device exist */
        if ((dev = __dev_get_by_name(net, iwr->ifr_name)) == NULL)
                return -ENODEV;

        /* A bunch of special cases, then the generic case...
         * Note that 'cmd' is already filtered in dev_ioctl() with
         * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */
        if (cmd == SIOCGIWSTATS)
                return standard(dev, iwr, cmd, info,
                                &iw_handler_get_iwstats);

#ifdef CONFIG_WEXT_PRIV
        if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
                return standard(dev, iwr, cmd, info,
                                iw_handler_get_private);
#endif

        /* Basic check */
        if (!netif_device_present(dev))
                return -ENODEV;

        /* New driver API : try to find the handler */
        handler = get_handler(dev, cmd);
        if (handler) {
                /* Standard and private are not the same */
                if (cmd < SIOCIWFIRSTPRIV)
                        return standard(dev, iwr, cmd, info, handler);
                else if (private)
                        return private(dev, iwr, cmd, info, handler);
        }
        return -EOPNOTSUPP;
}

去掉了老的 API

/* Old driver API : call driver ioctl handler */
	if (dev->netdev_ops->ndo_do_ioctl)
		return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd);

不知道它为什么这样做,从 iwpriv 返回的结果也可以看出,这里直接返回了 EOPNOTSUPP ,因为没有相应的 ioctl 去接手。

通过 strace 也能追踪到相应出错位置

strace iwpriv ra0

……
socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
brk(NULL)                               = 0x563a1154a000
brk(0x563a1156b000)                     = 0x563a1156b000
ioctl(3, SIOCGIWPRIV, 0x7ffc2176f390)   = -1 EOPNOTSUPP (不支持的操作)
……

# 解决

把新版内核中删掉的部分再给他加回去,如下:

/*
 * Main IOCTl dispatcher.
 * Check the type of IOCTL and call the appropriate wrapper...
 */
static int wireless_process_ioctl(struct net *net, struct iwreq *iwr,
                                  unsigned int cmd,
                                  struct iw_request_info *info,
                                  wext_ioctl_func standard,
                                  wext_ioctl_func private)
{
        struct net_device *dev;
        iw_handler      handler;

        /* Permissions are already checked in dev_ioctl() before calling us.
         * The copy_to/from_user() of ifr is also dealt with in there */

        /* Make sure the device exist */
        if ((dev = __dev_get_by_name(net, iwr->ifr_name)) == NULL)
                return -ENODEV;

        /* A bunch of special cases, then the generic case...
         * Note that 'cmd' is already filtered in dev_ioctl() with
         * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */
        if (cmd == SIOCGIWSTATS)
                return standard(dev, iwr, cmd, info,
                                &iw_handler_get_iwstats);

#ifdef CONFIG_WEXT_PRIV
        if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
                return standard(dev, iwr, cmd, info,
                                iw_handler_get_private);
#endif

        /* Basic check */
        if (!netif_device_present(dev))
                return -ENODEV;

        /* New driver API : try to find the handler */
        handler = get_handler(dev, cmd);
        if (handler) {
                /* Standard and private are not the same */
                if (cmd < SIOCIWFIRSTPRIV)
                        return standard(dev, iwr, cmd, info, handler);
                else if (private)
                        return private(dev, iwr, cmd, info, handler);
        }
// add begin
        /* Old driver API : call driver ioctl handler */
        if (dev->netdev_ops->ndo_do_ioctl)
                return dev->netdev_ops->ndo_do_ioctl(dev, (struct ifreq *)iwr, cmd);
// add end
        return -EOPNOTSUPP;
}

注意:ndo_do_ioctl 的第二个参数 ifr 要改为 (struct ifreq *)iwr。

# 参考

OpenWRT WIFI驱动的iwpriv兼容