最近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,那就不管了。