关键字

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. myBatis源码解析-类型转换篇(5)

    前言 开始分析Type包前,说明下使用场景.数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型.同理,数据库结果集返回的是jdbc类型,而我们需 ...

  2. 自绘引擎时代,为什么Flutter能突出重围?

    导语 | Flutter 框架是当下非常热门的跨端解决方案,能够帮助开发者通过一套代码库高效构建多平台精美应用,支持移动.Web.桌面等多端开发.但仍然有很多产品.设计.甚至开发同学并不了解 Flut ...

  3. Python Matplotlib绘图基础

    Matplotlib绘图基础 1.Figure和Subplot import numpy as np import matplotlib.pyplot as plt #创建一个Figure fig = ...

  4. java进阶(8)--匿名类

    一.概念 1.内部类:类的内部定义了一个类 2.内部类分类:静态内部类.实例内部类.局部内部类 3.缺点:内部类的可读性比较差,尽量不用 2.匿名内部类:局部内部类的一种   二.匿名内部类的缺点 1 ...

  5. Typescript node starter 3. App Router Controller

    Request request对象表示HTTP请求,并具有请求query字符串.参数.body.HTTP headers等的属性.除了添加新的属性和方法外,还包含原型的属性和方法. 随着系列文章的发布 ...

  6. linux驱动之内核多线程(一)

    本文摘自http://www.cnblogs.com/zhuyp1015/archive/2012/06/11/2545624.html Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进 ...

  7. maatwebsite lost precision when export long integer data

    Maatwebsite would lost precision when export long integer data, no matter string or int storaged in ...

  8. golang学习笔记:Interface类型断言详情

    原文链接:https://www.2cto.com/kf/201712/703563.html 1. 用于判断变量类型 demo如下: switch t := var.(type){ case str ...

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

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

  10. new操作符创建对象的四个步骤

    new操作符创建对象可以分为以下四个步骤: 创建一个空对象 将所创建对象的__proto__属性值设为构造函数的prototype的属性值 执行构造函数中的代码,构造函数中的this指向该对象 返回对 ...