按照高通SDX12平台产品规格,其支持RMNET、ECM、RNDIS、PPP、MBIM等拨号;但经测试,发现Windos下MBIM功能正常,而Linux发送MBIM命令均返回“error: couldn’t open the MbimDevice: Transaction timed out”错误,功能异常无法使用
调试MBIM功能,首先要确保SDX12端口模式支持MBIM,且在host上驱动加载正确,可以通过adb或串口进入到模块内,查看到高通提供了多个端口配置脚本便于我们进行端口的配置和切换:
此处以9063为例,可配置端口为AT、MBIM、ECM、RNDIS、DIAG、NEMA等端口:
#!/bin/sh
#
# Copyright (c) 2012-2015,2017, The Linux Foundation. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of The Linux Foundation nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE DISCLAIMED. IN NO
# EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# DESCRIPTION: RNDIS : ECM : MBIM
echo "Switching to composition number 0x9063" > /dev/kmsg
if [ "$1" = "y" ]; then
num="1"
else
num="0"
fi
if [ "$#" -ge 2 ]; then
delay=$2
else
delay="0"
fi
if [ "$#" -ge 3 ]; then
from_adb=$3
else
from_adb="n"
fi
run_9x15() {
echo 0 > /sys/class/android_usb/android$num/enable
echo 0x9063 > /sys/class/android_usb/android$num/idProduct
echo 0x05C6 > /sys/class/android_usb/android$num/idVendor
echo 239 > /sys/class/android_usb/android$num/bDeviceClass
echo 2 > /sys/class/android_usb/android$num/bDeviceSubClass
echo 1 > /sys/class/android_usb/android$num/bDeviceProtocol
echo rndis_qc:ecm_qc:usb_mbim > /sys/class/android_usb/android$num/functions
echo 1 > /sys/class/android_usb/android$num/remote_wakeup
echo 1 > /sys/class/android_usb/android0/f_rndis_qc/wceis
sleep $delay
echo 1 > /sys/class/android_usb/android$num/enable
}
run_9x25() {
echo 0 > /sys/class/android_usb/android$num/enable
echo 0x9063 > /sys/class/android_usb/android$num/idProduct
echo 0x05C6 > /sys/class/android_usb/android$num/idVendor
echo 239 > /sys/class/android_usb/android$num/bDeviceClass
echo 2 > /sys/class/android_usb/android$num/bDeviceSubClass
echo 1 > /sys/class/android_usb/android$num/bDeviceProtocol
echo rndis:ecm_qc:usb_mbim > /sys/class/android_usb/android$num/functions
echo BAM2BAM_IPA > /sys/class/android_usb/android$num/f_ecm_qc/ecm_transports
echo BAM2BAM_IPA > /sys/class/android_usb/android$num/f_usb_mbim/mbim_transports
echo 1 > /sys/class/android_usb/android$num/remote_wakeup
echo 1 > /sys/class/android_usb/android0/f_rndis/wceis
sleep $delay
echo 1 > /sys/class/android_usb/android$num/enable
}
run_9x35() {
pkill adbd
echo 0 > /sys/class/android_usb/android$num/enable
echo 9063 > /sys/class/android_usb/android$num/idProduct
echo 05C6 > /sys/class/android_usb/android$num/idVendor
echo 239 > /sys/class/android_usb/android$num/bDeviceClass
echo 2 > /sys/class/android_usb/android$num/bDeviceSubClass
echo 1 > /sys/class/android_usb/android$num/bDeviceProtocol
echo rndis_qc:ecm_qc:usb_mbim > /sys/class/android_usb/android$num/functions
echo BAM2BAM_IPA > /sys/class/android_usb/android0/f_ecm_qc/ecm_transports
echo BAM2BAM_IPA > /sys/class/android_usb/android0/f_usb_mbim/mbim_transports
echo BAM2BAM_IPA > /sys/class/android_usb/android0/f_rndis_qc/rndis_transports
echo 1 > /sys/class/android_usb/android$num/remote_wakeup
echo 1 > /sys/class/android_usb/android0/f_rndis_qc/wceis
sleep $delay
echo 1 > /sys/class/android_usb/android$num/enable
}
run_9607() {
pkill adbd
echo 0 > /sys/class/android_usb/android$num/enable
echo 9063 > /sys/class/android_usb/android$num/idProduct
echo 05C6 > /sys/class/android_usb/android$num/idVendor
echo 239 > /sys/class/android_usb/android$num/bDeviceClass
echo 2 > /sys/class/android_usb/android$num/bDeviceSubClass
echo 1 > /sys/class/android_usb/android$num/bDeviceProtocol
echo rndis:ecm:usb_mbim > /sys/class/android_usb/android$num/functions
echo BAM_DMUX > /sys/class/android_usb/android0/f_usb_mbim/mbim_transports
echo 1 > /sys/class/android_usb/android$num/remote_wakeup
echo 1 > /sys/class/android_usb/android0/f_rndis/wceis
sleep $delay
echo 1 > /sys/class/android_usb/android$num/enable
}
run_9650() {
pkill adbd
echo 0 > /sys/class/android_usb/android$num/enable
echo 9063 > /sys/class/android_usb/android$num/idProduct
echo 05C6 > /sys/class/android_usb/android$num/idVendor
echo 239 > /sys/class/android_usb/android$num/bDeviceClass
echo 2 > /sys/class/android_usb/android$num/bDeviceSubClass
echo 1 > /sys/class/android_usb/android$num/bDeviceProtocol
echo rndis_gsi:ecm_gsi:mbim_gsi > /sys/class/android_usb/android$num/functions
echo 1 > /sys/class/android_usb/android$num/remote_wakeup
sleep $delay
echo 1 > /sys/class/android_usb/android$num/enable
}
run_configfs() {
if [ $from_adb = "n" ]
then
pkill adbd
sleep 1
fi
cd /sys/kernel/config/usb_gadget/g1
rm os_desc/c* 2> /dev/null
rm configs/c*/f* 2> /dev/null
rm -rf configs/c.2 configs/c.3 2> /dev/null
mkdir configs/c.2
mkdir configs/c.2/strings/0x409
mkdir configs/c.3
mkdir configs/c.3/strings/0x409
echo 0x9063 > idProduct
echo 0x05c6 > idVendor
echo 239 > bDeviceClass
echo 2 > bDeviceSubClass
echo 1 > bDeviceProtocol
echo "RNDIS" > configs/c.1/strings/0x409/configuration
echo "ECM" > configs/c.2/strings/0x409/configuration
echo "MBIM" > configs/c.3/strings/0x409/configuration
ln -s functions/gsi.rndis configs/c.1/f1
ln -s functions/gsi.ecm configs/c.2/f1
ln -s functions/gsi.mbim configs/c.3/f1
ln -s configs/c.3 os_desc
echo 1 > os_desc/use
echo 0xA5 > os_desc/b_vendor_code
echo MSFT100 > os_desc/qw_sign
echo ALTRCFG > functions/gsi.mbim/os_desc/interface.MBIM/compatible_id
echo 3 > functions/gsi.mbim/os_desc/interface.MBIM/sub_compatible_id
echo 0xA0 > configs/c.1/bmAttributes
echo 0xA0 > configs/c.2/bmAttributes
echo 0xA0 > configs/c.3/bmAttributes
echo "binding UDC with Gadget..." $1
echo $1 > UDC
cd /
}
case `source /sbin/usb/target` in
*9x15* )
run_9x15 &
;;
*9x25* )
run_9x25 &
;;
*9x35* )
run_9x35 &
;;
*9607* )
run_9607 &
;;
*8909* )
run_9607 &
;;
*9640* )
run_9x35 &
;;
*9650* )
run_9650 &
;;
*sdxpoorwills* | *sdxprairie* | *sdxnightjar* )
udcname=`ls -1 /sys/class/udc | head -n 1`
run_configfs $udcname &
;;
* )
run_9650 &
;;
esac
通过上述脚本配置模块后进一步查看各端口加载驱动是否正常:
加载option驱动枚举出的串口,用于AT命令发送和抓取log:
加载cdc_mbim驱动枚举出的mbim端口,用于传输mbim数据:
拨号前,需要安装MBIM依赖环境:
安装mbim库:apt install libmbim-utils
Mbimcli库版本与ubuntu版本对应关系如下:
Ubuntu 16.04…………mbimcli 1.14
Ubuntu 18.04…………mbimcli 1.18
Ubuntu 20.04…………mbimcli 1.22
版本越高,功能越全,安装完成后,可以使用mbimcli –help 命令查看当前版本支持的mbim命令。
通过上述准备,发送mbimcli命令进行测试,返回error: couldn’t open the MbimDevice: Transaction timed out错误
通过分析host侧dmesg log,未见异常,进一步分析模块内mbimd log,可以看到log中最后打印“MBIM_OPEN Failed”,符合我们在执行mbimcli命令时“无法打开mbim设备”打印,筛选除关键log如下:
platform/src/qbi_os_linux.c::qbi_log_to_serial::1655:::Serial: qbi:MBIM_OPEN Received
…
platform/src/qbi_os_linux.c::qbi_os_linux_dpm_open::0250:::Sending QMI_DPM_OPEN_PORT_REQ
…
framework/src/qbi_qmi.c::qbi_qmi_proc_rsp_cb::2015:::Processing QMI response for svc_id 7 msg_id 0x0020 with txn iid 0
framework/src/qbi_msg.c::qbi_msg_send_open_done::1449:::Sending MBIM_OPEN rsp txn_id 1,ctx->txn_id_open 1
framework/src/qbi_msg.c::qbi_msg_send_open_rsp::1867::Error:Failure opening context ID 0: status 2
…
platform/src/qbi_os_linux.c::qbi_log_to_serial::1655:::Serial: qbi:MBIM_OPEN Failed
通过上面的log可以确认模块收到了打开MBIM端口的请求,但是打开失败。
进一步分析模块内gsi log,可以看到有如下打印:
gsi_ctrl_send_cpkt_tomodem: ctrl device android_mbim is not open
通过上面的log可以确认,模块内部android_mbim端口打开是失败的,涉及到模块内部由于没有相关代码,无法再深入分析。但由于windows下是ok的,因此抓取windows下mbimd和gsi log对比发现:在mbimd中,会设置data format数据格式,windows下是成功,而在linux下是失败的,linux失败mbimd log如下:
svc/src/qbi_svc_bc.c::qbi_svc_bc_radio_state_update_cache::6605:::HwRadioState changed from 0 to 1
svc/src/qbi_svc_bc.c::qbi_svc_bc_open_configure_data_path::2673:::Creating USB configure data path request
…
platform/src/qbi_hc_linux.c::qbi_hc_get_dl_ntb_max_size_v1::2878:::Maximum NTB size for USB: 0
platform/src/qbi_hc_linux.c::qbi_hc_get_dl_ntb_max_datagrams_v1::2756:::Maximum datagrams per NTB for USB: 0
svc/src/qbi_svc_bc.c::qbi_svc_bc_open_usb_configure_data_path_wda20_rsp_cb::2908::Error:Error setting data format!!! Error code 48
framework/src/qbi_svc.c::qbi_svc_proc_action::3004:::Processing action 4 for txn iid 0
windows下成功mbimd log如下:
qbi_hc_get_dl_ntb_max_size_v1::2878:::Maximum NTB size for USB: 16384
platform/src/qbi_hc_linux.c::qbi_hc_get_dl_ntb_max_size_v1::2878:::Maximum NTB size for USB: 16384
platform/src/qbi_hc_linux.c::qbi_hc_get_dl_ntb_max_datagrams_v1::2756:::Maximum datagrams per NTB for USB: 0
svc/src/qbi_svc_bc.c::qbi_svc_bc_open_usb_configure_data_path_wda20_rsp_cb::2956:::Successfully configured data format. dl_aggre_proto 3, ul_aggre_proto 3, ntb_max_size 10, ntb_max_dgrams 16384
对比可以发现,两者设置的Maximum NTB size for USB值存在差异,linux下是0,而windows下是16384,另外linux下QMI消息返回“Error setting data format!!! Error code 48”,而48通过查询QMI手册,可以得到错误原因是:QMI_ERR_TPDU_TYPE。这个错误正是由于传入的值0是非法的,最小值应该是2048:
The set_data_format failure due to dl_data_aggregation_max_size pass as 0 is invalid. The minimum value of dl_data_aggregation_max_size should be 2048
#define RMNET_DATA_FORMAT_MINIMUM_DL_DATA_AGG_SIZE (2048)
[5000/ 1] MSG 00:06:06.476 Data Services/Medium [ ds_qmi_wda.c 1594] Invalid dl_data_agg_max_size specified for MBIM: 0x0!
00:06:06.474 [0x1544] QMI_MCS_QCSI_PKT
wda_set_data_format_reqTlvs[6] {
Type = 0x16
Length = 4
dl_data_aggregation_max_size {
dl_data_aggregation_max_size = 0
}
}
进一步确认这个值的来源,从mbimd log中对应到qbi_hc_linux.c代码:
uint32 qbi_hc_get_dl_ntb_max_size_v1(const qbi_ctx_s *ctx)
{
…
if (!qbi_hc_pcie_is_enabled(ctx))
{
ret = ioctl(info->dev_fd, QBI_HC_LINUX_IOCTL_GET_NTB_SIZE, &ntb_max_size);
…
QBI_LOG_I_1("Maximum NTB size for USB: %d", ntb_max_size);
}
else
{
/* set max_size to 32*1024 */
ntb_max_size = 32768;
QBI_LOG_I_1("Maximum NTB size for PCIe: %d", ntb_max_size);
}
return ntb_max_size;
}
可以从上述代码中看到Maximum NTB size来自于ioctl,也就是来自于模块内部。
对比gsi log,发现windows上先通过USB_CDC_GET_NTB_PARAMETERS命令从模块内部获取到NTB INPUT SIZE值,再通过USB_CDC_SET_NTB_INPUT_SIZE设置到模块内部,而linux上仅有获取,没有设置,导致从ioctl中获取到的NTB INPUT SIZE值为0。
windows下gsi log:
[ 8.074870128/0x1ee151830df620f9] USB_CDC_GET_NTB_PARAMETERS
[ 8.074875128/0x1ee151830df62157] reqa1.80 v0000 i0000 l28
[ 8.075031326/0x1ee151830df62d11] USB_CDC_RESET_FUNCTION
[ 8.075035544/0x1ee151830df62d60] req21.05 v0000 i0000 l0
[ 8.075084867/0x1ee151830df63113] gsi_ctrl_send_cpkt_tomodem: Wake up read queue
[ 8.075136586/0x1ee151830df634f4] USB_CDC_SET_NTB_INPUT_SIZE
[ 8.075140857/0x1ee151830df63545] req21.86 v0000 i0000 l4
[ 8.075181378/0x1ee151830df63850] Received request packet
[ 8.075185597/0x1ee151830df638a0] gsi_ctrl_dev_read: cpkt size:0
[ 8.075191638/0x1ee151830df63915] gsi_ctrl_dev_read: copied 0 bytes to user
[ 8.075232524/0x1ee151830df63c26] dev:00000000
[ 8.075237107/0x1ee151830df63c7e] Set NTB INPUT SIZE 16384
[ 8.075336742/0x1ee151830df643f7] gsi_ctrl_dev_read: Exit 4096
[ 8.075384138/0x1ee151830df6478b] Requests list is empty. Wait.
[ 8.075462055/0x1ee151830df64d60] USB_CDC_RESET_FUNCTION
[ 8.075466378/0x1ee151830df64daf] req21.05 v0000 i0000 l0
[ 8.075522888/0x1ee151830df651ed] gsi_ctrl_send_cpkt_tomodem: Wake up read queue
[ 8.075579867/0x1ee151830df65633] USB_CDC_SET_NTB_INPUT_SIZE
[ 8.075584138/0x1ee151830df65685] req21.86 v0000 i0000 l4
[ 8.075673565/0x1ee151830df65d3a] dev:00000000
[ 8.075678097/0x1ee151830df65d91] Set NTB INPUT SIZE 16384
上节中的USB_CDC_GET_NTB_PARAMETERS等命令都是由host侧发给模块侧,host侧即为cdc_mbim驱动。在cdc_mbim驱动cdc_mbim_bind中会去调用cdc_ncm驱动中的cdc_ncm_bind_common函数:
\drivers\net\usb\cdc_mbim.c
static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
{
…
ret = cdc_ncm_bind_common(dev, intf, data_altsetting, dev->driver_info->data);
…
}
而cdc_ncm_bind_common中会对设备进行使用cdc_ncm_init初始化,初始化时通过发送USB_CDC_GET_NTB_PARAMETERS命令获取NTB参数:
\drivers\net\usb\cdc_ncm.c
int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting, int drvflags)
{
…
/* initialize basic device settings */
if (cdc_ncm_init(dev))
…
/* finish setting up the device specific data */
cdc_ncm_setup(dev);
…
}
static int cdc_ncm_init(struct usbnet *dev)
{
…
err = usbnet_read_cmd(dev, USB_CDC_GET_NTB_PARAMETERS,
USB_TYPE_CLASS | USB_DIR_IN
|USB_RECIP_INTERFACE,
0, iface_no, &ctx->ncm_parm,
sizeof(ctx->ncm_parm));
…
}
这个符合gsi log中NTB参数通过USB_CDC_GET_NTB_PARAMETERS命令获取;进一步分析,可看到cdc_ncm_bind_common中调用cdc_ncm_setup去启动设备,在cdc_ncm_setup中会调用cdc_ncm_update_rxtx_max进行rx和tx size的更新:
static int cdc_ncm_setup(struct usbnet *dev)
{
…
/* be conservative when selecting intial buffer size to
* increase the number of hosts this will work for
*/
def_rx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_RX,
le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize));
def_tx = min_t(u32, CDC_NCM_NTB_DEF_SIZE_TX,
le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize));
/* clamp rx_max and tx_max and inform device */
cdc_ncm_update_rxtx_max(dev, def_rx, def_tx);
…
}
cdc_ncm_update_rxtx_max中根据host默认最大值和通过USB_CDC_GET_NTB_PAR AMETERS命令获取到的值进行比较,在合理范围(2048<=val<=32768)内调用USB_CDC_SET_NTB_INPUT_SIZE命令进行参数的设置:
{
…
val = cdc_ncm_check_rx_max(dev, new_rx);
/* inform device about NTB input size changes */
if (val != ctx->rx_max) {
__le32 dwNtbInMaxSize = cpu_to_le32(val);
dev_info(&dev->intf->dev, "setting rx_max = %u\n", val);
/* tell device to use new size */
if (usbnet_write_cmd(dev, USB_CDC_SET_NTB_INPUT_SIZE,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE,
0, iface_no, &dwNtbInMaxSize, 4) < 0)
dev_dbg(&dev->intf->dev, "Setting NTB Input Size failed\n");
else
ctx->rx_max = val;
}
…
}
通过2.5节的分析和在驱动中添加log打印,确认在cdc_ncm_update_rxtx_max中,val值为16384,ctx->rx_max值也为16384,恰好不符合设置条件“val != ctx->rx_max”,导致无法下发USB_CDC_ SET_NTB_INPUT_SIZE命令进行参数的设置,通过对驱动该处逻辑的修改,使其符合条件,进行参数设置,进行验证,MBIM功能正常。但16384值是从模块内获取到的,且cdc_mbim驱动为内核自带,无法让每个客户去进行内核修改,因此修改cdc_mbim驱动判断条件是不合理的,需要进一步分析问题跟因并寻求更合理的解决方案。
通过进一步分析模块内gsi代码,可得出在gsi_setup中处理USB_CDC_GET_NTB_ PARAMETERS命令时是将mbim_gsi_ntb_parameters结构体中值返回给host,而在mbim_gsi_ntb_parameters结构体中成员dwNtbInMaxSize即为前面章节中说到的NTB INPUT SIZE,在f_gsi.h中大小定义为16384(0x4000)。
msm-5.4\drivers\usb\gadget\function\f_gsi.c
static int gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
…
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
…
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_GET_NTB_PARAMETERS:
log_event_dbg("USB_CDC_GET_NTB_PARAMETERS");
if (w_length == 0 || w_value != 0 || w_index != id)
break;
value = w_length > sizeof(mbim_gsi_ntb_parameters) ?
sizeof(mbim_gsi_ntb_parameters) : w_length;
memcpy(req->buf, &mbim_gsi_ntb_parameters, value);
break;
…
}
msm-5.4\drivers\usb\gadget\function\f_gsi.h
/* mbim device descriptors */
#define MBIM_NTB_DEFAULT_IN_SIZE (0x4000)
static struct usb_cdc_ncm_ntb_parameters mbim_gsi_ntb_parameters = {
.wLength = cpu_to_le16(sizeof(mbim_gsi_ntb_parameters)),
.bmNtbFormatsSupported = cpu_to_le16(USB_CDC_NCM_NTB16_SUPPORTED),
.dwNtbInMaxSize = cpu_to_le32(MBIM_NTB_DEFAULT_IN_SIZE),
.wNdpInDivisor = cpu_to_le16(4),
.wNdpInPayloadRemainder = cpu_to_le16(0),
.wNdpInAlignment = cpu_to_le16(4),
.dwNtbOutMaxSize = cpu_to_le32(0x4000),
.wNdpOutDivisor = cpu_to_le16(4),
.wNdpOutPayloadRemainder = cpu_to_le16(0),
.wNdpOutAlignment = cpu_to_le16(4),
.wNtbOutMaxDatagrams = cpu_to_le16(16),
};
通过查阅相关资料,对比其他项目如SDX55/SDX24等,另与高通交流,认为可将该MBIM_NTB_DEFAULT_IN_SIZE值改为与SDX55等项目保持一致,即31744(0x7C00)。
发送MBIM 命令进行拨号测试:
拨号成功后,使用脚本配置IP、路由:.
进行ping包测试:
Ping域名ok,功能正常,调试完成。
本文系统性分析MBIM拨号异常的过程,从上位机侧 linux、windows系统对比,到分析模块内部高通的QBI、usb gsi驱动,结合LOG以及源码给出一个清晰的问题排查方法。