Linux rndis_host 驱动的一个BUG与解决方案
关键字
rndis_host, linux, kernel, modem
综述
rndis 是微软定义的一套通讯方案。类似的协议还有 qmi/mbim/ecm/ncm。
rndis 协议足够简单,可靠。所以最近在使用一款 quectel 公司模块时采用的就是 rndis 模式。在linux 下 对应驱动是 rndis_host 驱动。windows 10下自带rndis 驱动!
拿到模块首先测速度! 发现模块下行速度 Windows 上速度比 Linux 高很多,而且上行速度则差不多! 单独对比 Linux,发现上行又比下行高很多。。。问题很奇怪!
分析
分析上行发包逻辑:
Linux rndis_host 发包函数代码
struct sk_buff *
rndis_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
{
struct rndis_data_hdr *hdr;
struct sk_buff *skb2;
unsigned len = skb->len;
// hexdump(">> ", skb->data, 14);
if (likely(!skb_cloned(skb)))
{
int room = skb_headroom(skb);
/* enough head room as-is? */
if (unlikely((sizeof *hdr) <= room))
goto fill;
/* enough room, but needs to be readjusted? */
room += skb_tailroom(skb);
if (likely((sizeof *hdr) <= room))
{
skb->data = memmove(skb->head + sizeof *hdr,
skb->data, len);
skb_set_tail_pointer(skb, len);
goto fill;
}
}
/* create a new skb, with the correct size (and tailpad) */
skb2 = skb_copy_expand(skb, sizeof *hdr, 1, flags);
dev_kfree_skb_any(skb);
if (unlikely(!skb2))
return skb2;
skb = skb2;
/* fill out the RNDIS header. we won't bother trying to batch
* packets; Linux minimizes wasted bandwidth through tx queues.
*/
fill:
hdr = __skb_push(skb, sizeof *hdr);
memset(hdr, 0, sizeof *hdr);
hdr->msg_type = cpu_to_le32(RNDIS_MSG_PACKET);
hdr->msg_len = cpu_to_le32(skb->len);
hdr->data_offset = cpu_to_le32(sizeof(*hdr) - 8);
hdr->data_len = cpu_to_le32(len);
/* FIXME make the last packet always be short ... */
return skb;
}
EXPORT_SYMBOL_GPL(rndis_tx_fixup);
上述函数很短,可以看到发包函数就是把上层传过来的数据包加上 rndis 协议报文头发出去,并没有别的处理! 需要注意的是,rndis 是支持报文聚合的!!!意思就是调用一次USB BULK OUT可以发送/接收多个IP报文!
所以可以看出,即使在上行未发生聚合的情况下,下行还比上行低,再结合Windows 下下行速度比较高那么问题就很明显了,一定是驱动收包有问题!
分析下行收包逻辑:
/*
* DATA -- host must not write zlps
*/
int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
int tm = 0;
/* This check is no longer done by usbnet */
if (skb->len < dev->net->hard_header_len)
return 0;
/* peripheral may have batched packets to us... */
while (likely(skb->len)) {
struct rndis_data_hdr *hdr = (void *)skb->data;
struct sk_buff *skb2;
u32 msg_type, msg_len, data_offset, data_len;
msg_type = le32_to_cpu(hdr->msg_type);
msg_len = le32_to_cpu(hdr->msg_len);
data_offset = le32_to_cpu(hdr->data_offset);
data_len = le32_to_cpu(hdr->data_len);
/* don't choke if we see oob, per-packet data, etc */
if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len
|| (data_offset + data_len + 8) > msg_len)) {
dev->net->stats.rx_frame_errors++;
netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n",
le32_to_cpu(hdr->msg_type),
msg_len, data_offset, data_len, skb->len);
return 0;
}
skb_pull(skb, 8 + data_offset);
/* at most one packet left? */
if (likely((data_len - skb->len) <= sizeof *hdr)) {
skb_trim(skb, data_len);
break;
}
/* try to return all the packets in the batch */
skb2 = skb_clone(skb, GFP_ATOMIC);
if (unlikely(!skb2))
break;
skb_pull(skb, msg_len - sizeof *hdr);
skb_trim(skb2, data_len);
usbnet_skb_return(dev, skb2);
}
/* caller will usbnet_skb_return the remaining packet */
return 1;
}
EXPORT_SYMBOL_GPL(rndis_rx_fixup);
收包代码稍微复杂点,因为收包需要考虑到聚合报文的情况!因此起了一个while循环判断。while 里面就是剥离rndis 报文头,并调用网卡收包函数的过程!
这里对skb 有两次偏移操作:
- skb_pull(skb, 8 + data_offset); 这一步从skb 去除当前消息的 rndis 报文头!
- skb_pull(skb, msg_len - sizeof *hdr); 因为skb payload 部分已经在skb2 有了一份clone,那么skb 当前的payload 就不重要了。因此,这里实际要做的是继续从skb剥离当前rndis 报文的数据部分(报文头已经剥离掉了)。这一步操作后,skb 将指向下一个rndis 报文的 rndis 报文头!
但是这里第2步逻辑错了,这里直接减去 rndis 报文头是错的! 因为rndis 报文的payload 之前并不一定全是协议头,payload 的偏移是头部offset 定义的。
解决方案
方案很简单,修改偏移计算逻辑!
/*
* DATA -- host must not write zlps
*/
int rndis_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
int tm = 0;
/* This check is no longer done by usbnet */
if (skb->len < dev->net->hard_header_len)
return 0;
/* peripheral may have batched packets to us... */
while (likely(skb->len)) {
struct rndis_data_hdr *hdr = (void *)skb->data;
struct sk_buff *skb2;
u32 msg_type, msg_len, data_offset, data_len;
msg_type = le32_to_cpu(hdr->msg_type);
msg_len = le32_to_cpu(hdr->msg_len);
data_offset = le32_to_cpu(hdr->data_offset);
data_len = le32_to_cpu(hdr->data_len);
/* don't choke if we see oob, per-packet data, etc */
if (unlikely(msg_type != RNDIS_MSG_PACKET || skb->len < msg_len
|| (data_offset + data_len + 8) > msg_len)) {
dev->net->stats.rx_frame_errors++;
netdev_dbg(dev->net, "bad rndis message %d/%d/%d/%d, len %d\n",
le32_to_cpu(hdr->msg_type),
msg_len, data_offset, data_len, skb->len);
return 0;
}
skb_pull(skb, 8 + data_offset);
/* at most one packet left? */
if (likely((data_len - skb->len) <= sizeof *hdr)) {
skb_trim(skb, data_len);
break;
}
/* try to return all the packets in the batch */
skb2 = skb_clone(skb, GFP_ATOMIC);
if (unlikely(!skb2))
break;
skb_pull(skb, msg_len - data_offset - 8); // here is what I fixed
skb_trim(skb2, data_len);
usbnet_skb_return(dev, skb2);
}
/* caller will usbnet_skb_return the remaining packet */
return 1;
}
EXPORT_SYMBOL_GPL(rndis_rx_fixup);
Linux rndis_host 驱动的一个BUG与解决方案的更多相关文章
- linux 设备驱动概述
linux 设备驱动概述 目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer): 主要利用C库函数和 ...
- 介绍linux设备驱动编程
目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer): 主要利用C库函数和Linux API进行应用 ...
- 如何在嵌入式Linux上开发一个语音通信解决方案
开发一个语音通信解决方案是一个软件项目.既然是软件项目,就要有相应的计划:有多少功能,安排多少软件工程师去做,这些工程师在这一领域的经验如何,是否需要培训,要多长时间做完,中间有几个主要的milest ...
- 记录Window系统下myeclipes连接linux下mysql所出现的一个bug
记录myeclipes远程连接mysql所出现的一个bug 今天在玩框架hibernate时,出现一个非常费解的bug,话不多说,先看bug Access denied for user 'root' ...
- Linux 网卡驱动学习(一)(分析一个虚拟硬件的网络驱动样例)
在Linux,网络分为两个层,各自是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层. 网络堆栈是硬件中独立出来的部分.主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层 ...
- pycharm下: conda installation is not found ----一个公开的bug的解决方案
pycharm conda installation is not found ----一个公开的bug的解决方案 pycharm+anaconda 是当前的主流的搭建方案,但是常出现上述问题. ...
- linux设备驱动第三篇:如何实现一个简单的字符设备驱动
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
- linux设备驱动第三篇:如何写一个简单的字符设备驱动?
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
- Linux Framebuffer驱动剖析之中的一个—软件需求
嵌入式企鹅圈将以本文作为2015年的终结篇,以回应第一篇<Linux字符设备驱动剖析>.嵌入式企鹅圈一直专注于嵌入式Linux和物联网IOT双方面的原创技术分享,稍后会公布嵌入式企鹅圈的2 ...
随机推荐
- 如何通过命令行简单的执行C程序
如何通过命令行简单的执行C语言编写的程序 首先,我们知道C语言程序都是以xxx.c结尾的,这在Windows系统和Linux系统都是一样的.其次,C程序的执行过程为四步:预处理--编译--汇编-- ...
- Mybatis中选择语句的使用:<choose>标签、分区排序 Row_num() over ()函数的使用呢
1.Mybatis中数据库语句的选择 使用: <choose> <when test="relationType=='L'"> ...
- 【算法•日更•第四十七期】Mac与windows系统的差别
小编最近装了个Mac系统,因为小编已经有笔记本可以用linux了,所以就决定在台式机上装个双系统,结果一不小心把Mac装在C盘上了,哎,说多了都是泪啊. 其实用了Mac之后才发现windows特别好用 ...
- PAT 2-10. 海盗分赃(25)
题目链接:http://www.patest.cn/contests/ds/2-10 解题思路:参考:http://blog.csdn.net/linsheng9731/article/details ...
- Spring官方宣布:新的Spring OAuth2.0授权服务器已经来了
1. 前言 记不记得之前发过一篇文章Spring 官方发起Spring Authorization Server 项目.该项目是由Spring Security主导的一个社区驱动的.独立的孵化项目.由 ...
- ondyari / FaceForensics配置指南
https://github.com/ondyari/FaceForensics 安装配置方法: $ git clone https://github.com/ondyari/FaceForensic ...
- windows下cmd命令行计算文件hash值
命令:certutil -hashfile certutil -hashfile D:\.exe MD5 certutil -hashfile D:\.exe SHA1 certutil -hashf ...
- vue前端工程化
今日目标 1.能够了解模块化的相关规范 2.了解webpack3.了解使用Vue单文件组件4.能够搭建Vue脚手架 5.掌握Element-UI的使用 1.模块化的分类 A.浏览器端的模块化 1) ...
- python练习 英文字符的鲁棒输入+数字的鲁棒输入
鲁棒 = Robust 健壮 英文字符的鲁棒输入 描述 获得用户的任何可能输入,将其中的英文字符进行打印输出,程序不出现错误. ...
- row_number()分页返回结果顺序不确定
之前通过row_number()实现分页查询时: select top [PageSize] * from ( select row_number() over (order by id desc) ...