关键字

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 有两次偏移操作:

  1. skb_pull(skb, 8 + data_offset); 这一步从skb 去除当前消息的 rndis 报文头!
  2. 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与解决方案的更多相关文章

  1. linux 设备驱动概述

    linux 设备驱动概述 目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer):       主要利用C库函数和 ...

  2. 介绍linux设备驱动编程

    目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer):       主要利用C库函数和Linux API进行应用 ...

  3. 如何在嵌入式Linux上开发一个语音通信解决方案

    开发一个语音通信解决方案是一个软件项目.既然是软件项目,就要有相应的计划:有多少功能,安排多少软件工程师去做,这些工程师在这一领域的经验如何,是否需要培训,要多长时间做完,中间有几个主要的milest ...

  4. 记录Window系统下myeclipes连接linux下mysql所出现的一个bug

    记录myeclipes远程连接mysql所出现的一个bug 今天在玩框架hibernate时,出现一个非常费解的bug,话不多说,先看bug Access denied for user 'root' ...

  5. Linux 网卡驱动学习(一)(分析一个虚拟硬件的网络驱动样例)

    在Linux,网络分为两个层,各自是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层. 网络堆栈是硬件中独立出来的部分.主要用来支持TCP/IP等多种协议,网络设备驱动层是连接网络堆栈协议层 ...

  6. pycharm下: conda installation is not found ----一个公开的bug的解决方案

    pycharm  conda installation is not  found ----一个公开的bug的解决方案 pycharm+anaconda 是当前的主流的搭建方案,但是常出现上述问题. ...

  7. linux设备驱动第三篇:如何实现一个简单的字符设备驱动

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  8. linux设备驱动第三篇:如何写一个简单的字符设备驱动?

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  9. Linux Framebuffer驱动剖析之中的一个—软件需求

    嵌入式企鹅圈将以本文作为2015年的终结篇,以回应第一篇<Linux字符设备驱动剖析>.嵌入式企鹅圈一直专注于嵌入式Linux和物联网IOT双方面的原创技术分享,稍后会公布嵌入式企鹅圈的2 ...

随机推荐

  1. angular中a标签带请求头下载excel

    <!DOCTYPE html> <html lang="en" ng-app="app"> <head> <meta ...

  2. 如何限制ip访问Oracle数据库

    一.概述 本文将给大家介绍如何限制某个ip或某个ip段才能访问Oracle数据库 通过sqlnet.ora 通过/etc/hosts.deny和/etc/hosts.allow 通过iptables ...

  3. JS中new Date()用法及获取服务器时间

    1.获取服务器时间: var now = new Date($.ajax({async: false}).getResponseHeader("Date")); 2.new Dat ...

  4. ssh连接:Socket error Event: 32 Error: 10053.

    今天在使用xshell连接刚装的linux系统的时候,发现无法建立连接,会报如下错误: Connecting to 192.168.21x.x:22...Connection established. ...

  5. Jmeter 常用函数(20)- 详解 __counter

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.htm 作用 计数器,跟配置元件里面的计数器作用类似哦 ...

  6. 5.SSH 免密码登陆

    SSH 免密码登陆 serverA 机器上 userA 用户,想要免密钥登陆到serverB机器上 1.集群中的所有机器 安装ssh 服务端sudo apt-get updatesudo apt-ge ...

  7. 下载 golang.org/x 包出错不用代理的解决办法

    原文链接:https://www.jianshu.com/p/6fe61053c8aa?utm_campaign=maleskine&utm_content=note&utm_medi ...

  8. 如何获取东方财富文华新浪财经实时行情数据API接口

    BIGI行情期货外汇股指A股期权实时行情数据文华新浪财经API接口新浪财经并非实时行情数据源,所以获取的行情数据源也并非实时的.以下介绍的方法和新浪财经获取行情数据源的方法是一致的.需要实时行情数据源 ...

  9. 按钮改变和控制div的形状的html,JavaScript代码

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 算法-图(3)用顶点表示活动的网络(AOV网络)Activity On Vertex NetWork

    对于给定的AOV网络,必须先判断是否存在有向环. 检测有向环是对AOV网络构造它的拓扑有序序列,即将各个顶点排列成一个线性有序的序列,使得AOV网络中所有直接前驱和直接后继关系都能得到满足. 这种构造 ...