OpenWRT WIFI驱动的iwpriv兼容

最近OpenWRT升级到18.06.2后,内核变为4.14,移植WIFI驱动的过程中遇到了困难,原因是WIFI驱动比较老依赖WEXT的旧接口ndo_do_ioctl方式,导致运行iwpriv出错。

以下是解决问题的思路:

一、用strace调试iwpriv

root@XXX:~# strace iwpriv ra0
execve("/usr/sbin/iwpriv", ["iwpriv", "ra0"], 0x7feeb574 /* 12 vars */) = 0
set_thread_area(0x77fc4dc0)             = 0
set_tid_address(0x77fbdd28)             = 5485
open("/etc/ld-musl-mipsel-sf.path", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/lib/libgcc_s.so.1", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fcntl64(3, F_SETFD, FD_CLOEXEC)         = 0
fstat64(3, {st_mode=S_IFREG|0644, st_size=78095, ...}) = 0
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\10\0\1\0\0\0p(\0\0004\0\0\0"..., 936) = 936
mmap2(NULL, 147456, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x77ef5000
mmap2(0x77f18000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x13000) = 0x77f18000
close(3)                                = 0
mprotect(0x418000, 4096, PROT_READ)     = 0
socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
ioctl(3, SIOCGIWPRIV, 0x7f8e699c)       = -1 EOPNOTSUPP (Not supported)
writev(2, [{iov_base="eth0.2    no private ioctls.\n\n", iov_len=30}, {iov_base=NULL, iov_len=0}], 2eth0.2    no private ioctls.

) = 30
close(3)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

分析到iwpriv调用了socket ioctl 获取SIOCGIWPRIV返回的私有ioctl信息,但是返回EOPNOTSUPP不支持,下一步重点就在内核的sock_ioctl是怎么处理SIOCGIWPRIV?

二、查看内核代码(4.14):

    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);
        if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {
            err = dev_ioctl(net, cmd, argp);
        } else
    #ifdef CONFIG_WEXT_CORE
        if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
            err = dev_ioctl(net, cmd, argp); // 看这里
        } else
    #endif
    ......

发现sock_ioctl有WEXT的特殊处理,因为SIOCGIWPRIV是在SIOCIWFIRST和SIOCIWLAST之间,直接调用了dev_ioctl(好像是/dev/下设备文件的ioctl),直接看dev_ioctl:

    int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
    {
        struct ifreq ifr;
        int ret;
        char *colon;

        /* One special case: SIOCGIFCONF takes ifconf argument
           and requires shared lock, because it sleeps writing
           to user space.
         */

        if (cmd == SIOCGIFCONF) {
            rtnl_lock();
            ret = dev_ifconf(net, (char __user *) arg);
            rtnl_unlock();
            return ret;
        }
        if (cmd == SIOCGIFNAME)
            return dev_ifname(net, (struct ifreq __user *)arg);

        /*
         * Take care of Wireless Extensions. Unfortunately struct iwreq
         * isn't a proper subset of struct ifreq (it's 8 byte shorter)
         * so we need to treat it specially, otherwise applications may
         * fault if the struct they're passing happens to land at the
         * end of a mapped page.
         */
        if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
            struct iwreq iwr;

            if (copy_from_user(&iwr, arg, sizeof(iwr)))
                return -EFAULT;

            iwr.ifr_name[sizeof(iwr.ifr_name) - 1] = 0;

            return wext_handle_ioctl(net, &iwr, cmd, arg);  //看这里
        }

这里直接调用了wext_handle_ioctl,注意用了iwreq的结构,这个是和ifreq结构类似的,但是比ifreq小8个字节(这个导致后面的解决补丁有隐患,因为补丁是直接把iwreq转换成了ifreq,如果超过32个字节直接就截掉了)。

中间省略步骤,最后跟踪到wireless_process_ioctl函数里:

/*
 * 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;
}

这里压根看不到哪里调用了WIFI驱动配置用的ndo_do_ioctl入口,可以确定的是用了net_device->wireless_handlers获取private和standard的handler方式处理,但没有ndo_do_ioctl什么事情,一番google后发现2017年的时候某一版内核删掉了ndo_do_ioctl支持

diff --git a/net/wireless/wext-core.c b/net/wireless/wext-core.c
index 1a4db6790e20..24ba8a99b946 100644
--- a/net/wireless/wext-core.c
+++ b/net/wireless/wext-core.c
@@ -957,9 +957,6 @@  static int wireless_process_ioctl(struct net *net, struct ifreq *ifr,
        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;
 }

最后的解决办法为了兼容,加上上面删掉的代码,不过要把ifr替换成(struct iwreq *)iwr,隐患是毕竟iwreq和ifreq相似但是大小不同,一个是32个字节,一个是40个,可能会有问题,不过我测试了一下貌似OK,那就不管了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注