Linux主机USB RNDIS网卡驱动实现不完整导致的一例问题
某通信模块设备,通过USB提供RDNIS和ECM网卡功能。在实际应用中发现,USB RNDIS网卡模式下,当使用AT指令以不同的CID拨号的时候,在Windows主机上能正常拨号成功,但在Linux主机上却会发生拨号失败的情况。作为对比,在同样的测试环境和测试方法下,USB ECM网卡则没有这种异常。
测试流程概括如下:
- 设备侧已经配置为USB RNDIS模式
- 主机侧通过AT指令以CID1拨号成功
- 测试网络功能,主机和设备侧可以ping通
- 主机侧通过AT指令断开CID1拨号
- 主机侧通过AT指令以CID2拨号失败,主机和设备侧不能ping通
以上是问题背景。
USB ECM是USB-IF定义的CDC类规范下的一个子类规范,全称Ethernet Networking Control Model;RNDIS是微软为即插即用的以太网设备制定的一种规范。实现这两种协议的USB设备,通过USB线接入主机后,会在主机侧和设备侧各生成一张网卡。两侧的网卡处在同一个网段,进行网络通信,数据承载通路是USB。下图是从微软官网摘抄的RNDIS框架图:
https://docs.microsoft.com/en-us/windows-hardware/drivers/network/overview-of-remote-ndis--rndis-
经调查,Linux主机上RNDIS拨号测试失败,主要原因在于:当第一次拨号成功后,断开拨号时,Linux主机上的USB网卡IP地址并没有消失,后续以不同CID拨号后,Linux也没有发起DHCP请求包,DHCP过程失败,IP地址未更新。此时,如果将USB拔插一下就可恢复正常。
通过AT指令通知设备端断开拨号时,设备侧会有down USB网卡的动作,down USB网卡的过程中,设备侧RNDIS会上报rndis disconnect消息来通知主机侧。主机侧可以根据这个消息做相应处理。
在Ubuntu主机和Windows主机上测试断开拨号操作时,在设备端抓取的kernel log片段如下。可以看到,无论是在Windows主机上还是Ubuntu主机上,设备端确实在断开拨号时上报了rndis disconnect消息。
- Ubuntu主机环境,设备端log:
- root@udx710-module:~#
- [ 324.516525] c0 configfs-gadget gadget: rndis_close
- [ 324.521239] c0 rndis_set_param_medium: 0 0
- [ 324.525296] c0 rndis_signal_disconnect
- [ 324.529023] c0 rndis_indicate_status_msg: status 1073807372
-
- Windows主机环境,设备端log:
- root@udx710-module:~#
- [ 191.340507] c1 configfs-gadget gadget: rndis_close
- [ 191.345223] c1 rndis_set_param_medium: 0 0
- [ 191.349285] c1 rndis_signal_disconnect
- [ 191.353008] c1 rndis_indicate_status_msg: status 1073807372
- [ 191.364621] c1 configfs-gadget gadget: rndis reqa1.01 v0000 i0000 l4096
设备端动作
设备端关键代码如下:
rndis_close -> rndis_signal_disconnect -> rndis_indicate_status_msg
- drivers/usb/gadget/function/rndis.c
-
- int rndis_signal_disconnect(struct rndis_params *params)
- {
- params->media_state = RNDIS_MEDIA_STATE_DISCONNECTED;
- return rndis_indicate_status_msg(params, RNDIS_STATUS_MEDIA_DISCONNECT);
- }
-
- /*
- * Device to Host Comunication
- */
- static int rndis_indicate_status_msg(struct rndis_params *params, u32 status)
- {
- rndis_indicate_status_msg_type *resp;
- rndis_resp_t *r;
-
- if (params->state == RNDIS_UNINITIALIZED)
- return -ENOTSUPP;
-
- r = rndis_add_response(params, sizeof(rndis_indicate_status_msg_type));
- if (!r)
- return -ENOMEM;
- resp = (rndis_indicate_status_msg_type *)r->buf;
-
- resp->MessageType = cpu_to_le32(RNDIS_MSG_INDICATE);
- resp->MessageLength = cpu_to_le32(20);
- resp->Status = cpu_to_le32(status);
- resp->StatusBufferLength = cpu_to_le32(0);
- resp->StatusBufferOffset = cpu_to_le32(0);
-
- params->resp_avail(params->v);
- return 0;
- }
-
- drivers/usb/gadget/function/f_rndis.c
-
- static void rndis_response_available(void *_rndis)
- {
- struct f_rndis *rndis = _rndis;
- struct usb_request *req = rndis->notify_req;
- struct usb_composite_dev *cdev = rndis->port.func.config->cdev;
- __le32 *data = req->buf;
- int status;
-
- if (atomic_inc_return(&rndis->notify_count) != 1)
- return;
-
- /* Send RNDIS RESPONSE_AVAILABLE notification; a
- * USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too
- *
- * This is the only notification defined by RNDIS.
- */
- data[0] = cpu_to_le32(1);
- data[1] = cpu_to_le32(0);
-
- status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
- if (status) {
- atomic_dec(&rndis->notify_count);
- DBG(cdev, "notify/0 --> %d\n", status);
- }
- }
rndis_indicate_status_msg 会做两件事:
1)把RNDIS_MSG_INDICATE message放入response queue,message中带了status(RNDIS_STATUS_MEDIA_DISCONNECT)。
2)调用 resp_avail 发 RESPONSE_AVAILABLE notification(最终调用到rndis_response_available @ f_rndis.c),通过interrupt IN端点发出去,这个数据中没有包含rndis连接状态信息,只是单纯的上报RESPONSE_AVAILABLE (0x00000001)。
主机端动作
1)按微软的RNDIS规范,Host收到RESPONSE_AVAILABLE后,接下来需要往control端点发 GET_ENCAPSULATED_RESPONSE request 才能读取设备端的 response,得到rndis连接状态。
Upon receiving the RESPONSE_AVAILABLE notification, the host reads the control message from the Control endpoint using a GET_ENCAPSULATED_RESPONSE transfer, defined in the following table.
BmRequestType bRequest wValue wIndex 0xA1 0x01 0x0000 https://docs.microsoft.com/en-us/windows-hardware/drivers/network/control-channel-characteristics
2)再看rndis host驱动:
rndis_status @ rndis_host.c 函数没有做实际动作。
- drivers/net/usb/rndis_host.c
- void rndis_status(struct usbnet *dev, struct urb *urb)
- {
- netdev_dbg(dev->net, "rndis status urb, len %d stat %d\n",
- urb->actual_length, urb->status);
- // FIXME for keepalives, respond immediately (asynchronously)
- // if not an RNDIS status, do like cdc_status(dev,urb) does
- }
-
- static const struct driver_info rndis_info = {
- .description = "RNDIS device",
- .flags = FLAG_ETHER | FLAG_POINTTOPOINT | FLAG_FRAMING_RN | FLAG_NO_SETINT,
- .bind = rndis_bind,
- .unbind = rndis_unbind,
- .status = rndis_status,
- .rx_fixup = rndis_rx_fixup,
- .tx_fixup = rndis_tx_fixup,
- };
也就是说,Linux主机RNDIS驱动没有严格按照RNDIS协议流程去读取RNDIS_STATUS_MEDIA_DISCONNECT消息,导致它无法获知设备端RNDIS网卡断开的状态,进而无法正确作出网络状态改变的相关处理。
而且作者也在代码注释中表明了态度:强烈建议不要使用RNDIS,而应使用CDC以太网(ECM,NCM,EEM等)这类非专有(non-proprietary)的替代方案。USB CDC规范是USB-IF制定的,RNDIS是微软制定的。
- /*
- * RNDIS is NDIS remoted over USB. It's a MSFT variant of CDC ACM ... of
- * course ACM was intended for modems, not Ethernet links! USB's standard
- * for Ethernet links is "CDC Ethernet", which is significantly simpler.
- *
- * NOTE that Microsoft's "RNDIS 1.0" specification is incomplete. Issues
- * include:
- * - Power management in particular relies on information that's scattered
- * through other documentation, and which is incomplete or incorrect even
- * there.
- * - There are various undocumented protocol requirements, such as the
- * need to send unused garbage in control-OUT messages.
- * - In some cases, MS-Windows will emit undocumented requests; this
- * matters more to peripheral implementations than host ones.
- *
- * Moreover there's a no-open-specs variant of RNDIS called "ActiveSync".
- *
- * For these reasons and others, ** USE OF RNDIS IS STRONGLY DISCOURAGED ** in
- * favor of such non-proprietary alternatives as CDC Ethernet or the newer (and
- * currently rare) "Ethernet Emulation Model" (EEM).
- */
最后顺便看看为什么ECM没有问题。
ECM设备侧,ecm_close -> ecm_notify -> ecm_do_notify -> 通过interrupt IN端点发出去,这个数据中直接带了ecm连接状态,无需Host专门再发另外的request读取这个状态。
- static void ecm_do_notify(struct f_ecm *ecm)
- {
- struct usb_request *req = ecm->notify_req;
- struct usb_cdc_notification *event;
- ...
- event = req->buf;
- switch (ecm->notify_state) {
- ...
- case ECM_NOTIFY_CONNECT:
- event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
- if (ecm->is_open)
- event->wValue = cpu_to_le16(1);
- else
- event->wValue = cpu_to_le16(0);
- event->wLength = 0;
- req->length = sizeof *event;
-
- DBG(cdev, "notify connect %s\n",
- ecm->is_open ? "true" : "false");
- ecm->notify_state = ECM_NOTIFY_SPEED;
- break;
- ...
- }
ECM主机侧,usbnet_cdc_status @ cdc_ether.c 函数中有处理ecm connection消息并调用usbnet_link_change。
- void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)
- {
- struct usb_cdc_notification *event;
- ...
- event = urb->transfer_buffer;
- switch (event->bNotificationType) {
- case USB_CDC_NOTIFY_NETWORK_CONNECTION:
- netif_dbg(dev, timer, dev->net, "CDC: carrier %s\n",
- event->wValue ? "on" : "off");
- usbnet_link_change(dev, !!event->wValue, 0);
- break;
- ...
- }
- }
-
- static const struct driver_info cdc_info = {
- .description = "CDC Ethernet Device",
- .flags = FLAG_ETHER | FLAG_POINTTOPOINT,
- .bind = usbnet_cdc_bind,
- .unbind = usbnet_cdc_unbind,
- .status = usbnet_cdc_status,
- .set_rx_mode = usbnet_cdc_update_filter,
- .manage_power = usbnet_manage_power,
- };
版权所有,转载请注明出处。
文章会同步到“大鱼嵌入式”,欢迎关注,一起交流。
Linux主机USB RNDIS网卡驱动实现不完整导致的一例问题的更多相关文章
- 【转】 linux内核移植和网卡驱动(二)
原文网址:http://blog.chinaunix.net/uid-29589379-id-4708911.html 一,内核移植步骤: 1, 修改顶层目录下的Makefile ARCH ...
- Linux下USB转串口的驱动【转】
转自:http://www.linuxidc.com/Linux/2011-02/32218.htm Linux发行版自带usb to serial驱动,以模块方式编译驱动,在内核源代码目录下运行Ma ...
- linux下安装编译网卡驱动的方法
安装linux操作系统后发现没有网卡驱动,表现为 system → Administration → Network下Hardware列表为空. 以下为安装编译网卡驱动的过程,本人是菜鸟,以下是我从网 ...
- LINUX内核升级-更新网卡驱动
因项目需要,将当前内核(2.6.32-220.el6.x86_64)升级到目标内核(2.6.33-110.el6.x86_64),但是编译的目标 内核(2.6.33-110.el6.x86_64)的对 ...
- LINUX 内核移植以及网卡驱动添加
我用的板子是sama5d3xek,原来板子内核是linux-at91-3.13,升级使用linux-at91-4.10 首先去官网下载一个linux—at91-4.10压缩包,然后在ubuntu里解压 ...
- linux 保留内核中sas驱动的加载导致crash问题
[root@localhost ~]# uname -a Linux localhost.localdomain -.el7.x86_64 问题描述,在crash的时候,小内核因为分配中断号失败而触发 ...
- 基于tiny4412的Linux内核移植 -- DM9621NP网卡驱动移植(四)
作者信息 作者: 彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台简介 开发板:tiny4412ADK + S700 + 4GB Flash 要移植的内核版本 ...
- AM335X的USB otg网卡(RNDIS /Ethernet Gadget)调试
重新编译内核(2.6.29) 2.6.29内核 Device Drivers ---> USB support ---> USB Gadget Support ...
- 新装Linux系统没有网卡驱动的解决办法和步骤
Linux下查看网卡驱动和版本信息 - CSDN博客 https://blog.csdn.net/guyan1101/article/details/72770424/ 检查网卡是否加载 - Linu ...
随机推荐
- sqli-labs系列——第三关
less3 判断注入类型 这第三关有点意思,是一个带括号的数字型注入,这里需要闭合它的括号,之前遇到过很多这样的站,它的sql语句一般都是这样的: $sql = select * from user ...
- 【linux】驱动-8-一文解决设备树
目录 前言 8. Linux设备树 8.1 设备树简介 8.2 设备树框架 8.2.1 设备树格式 8.2.1.1 DTS 文件布局 8.2.1.2 node 格式 8.2.1.3 propertie ...
- shell算数和逻辑运算
算术运算 Shell允许在某些情况下对算术表达式进行求值,比如:let和declare 内置命令,(( ))复合命令和算术扩 展.求值以固定宽度的整数进行,不检查溢出,尽管除以0 被困并标记为错误.运 ...
- Asp.Net Core 5 REST API - Step by Step
翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...
- 基于vite2的react脚手架
vite-react-boilerplate 开发编译 yarn start 启动开发 yarn build 启动编译 代码质量和风格 husky/lint-staged/eslint/prettie ...
- 结对编程_stage1
项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 结对项目-第一阶段 我在这个课程的目标是 从实践中学习软件工程相关知识(结构化分析和设计方法.敏捷开发方法. ...
- Spring Boot自动配置原理
使用Spring Boot之后,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这 是如何做到的? 一切魔力的开始,都是从我们的main函数来的,所以我们再次来 ...
- 11. man page,info page
Linux系统中的命令可分为内部命令和外部命令.内部命令,又称为内建命令(builtin).怎么区分内部命令和外部命令了? 输入man bash命令,就可查看所有的内部命令. 如何查看命令使用方法 内 ...
- IdentityServer4+OAuth2.0+OpenId Connect 详解
一 Oauth 2.0 1 定义 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. ...
- 0702-计算机视觉工具包torchvision
0702-计算机视觉工具包torchvision 目录 一.torchvision 概述 二.通过 torchvision 加载模型 三.通过 torchvision 加载并处理数据集 四.通过 to ...