I’m making a Raspberry Pi to put at a “remote” location, and I needed network connectivity. I had an old USB 3G dongle lying around. It appears to be a Vodafone-branded Huawei K4302 modem, supporting HSPA+ (no LTE).
The modem presents itself on the USB bus as follows:
Bus 001 Device 005: ID 12d1:1f1c Huawei Technologies Co., Ltd.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 255
bMaxPacketSize0 64
idVendor 0x12d1 Huawei Technologies Co., Ltd.
idProduct 0x1f1c
bcdDevice 1.02
iManufacturer 1 Vodafone(Huawei)
iProduct 2 HUAWEI Mobile
iSerial 3 FFFFFFFFFFFFFFFF
bNumConfigurations 2
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 32
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 8 Mass Storage
bInterfaceSubClass 6 SCSI
bInterfaceProtocol 80 Bulk-Only
iInterface 4 Mass Storage
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 87
bNumInterfaces 2
bConfigurationValue 2
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 500mA
Interface Association:
bLength 8
bDescriptorType 11
bFirstInterface 0
bInterfaceCount 2
bFunctionClass 2 Communications
bFunctionSubClass 14
bFunctionProtocol 0
iFunction 8 K4203
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 14
bInterfaceProtocol 0
iInterface 5 CDC Network Control Model (NCM)
CDC Header:
bcdCDC 1.10
CDC MBIM:
bcdMBIMVersion 1.00
wMaxControlMessage 1024
bNumberFilters 16
bMaxFilterSize 20
wMaxSegmentSize 1500
bmNetworkCapabilities 0x20
8-byte ntb input size
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0010 1x 16 bytes
bInterval 9
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0 Unused
bInterfaceProtocol 2
iInterface 6 CDC Network Data
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0 Unused
bInterfaceProtocol 2
iInterface 6 CDC Network Data
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 0
Device Qualifier (for other device speed):
bLength 10
bDescriptorType 6
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 255
bMaxPacketSize0 64
bNumConfigurations 2
Device Status: 0x0001
Self Powered
Its a fairly typical “flip-flop” device that starts out by pretending to be a storage device (CD-ROM is this case), but can be switched to the actual modem. Under linux, this switching is done by usb_modeswitch
USB reset
Normally, this all goes automatically. But it didn’t.
Every time usb_modeswitch tried to switch the dongle, I got:
usb usb1-port3: disabled by hub (EMI?), re-enabling...
Followed by a complete reset of everything attached to the USB-bus (including wired ethernet). The message (and the search results about the message) seem to be unrelated to mode switching. I don’t think it’s a power issue. The Raspberry Pi is capable to provide 1.2A of power to its USB ports. Since the 3G modem is the only thing plugged in, and a single USB port should not require more than 500mA, I should have 700mA headroom.
The stick is plugged directly into the port, without any extension cord that could pick up EMI, but I tried adding a cord anyway, but got the same behaviour.
But I found it peculiar that the bus reset happens right at the time of the mode-switch, so I looked into that.
Alternative switching method
I found a page describing another switching method, that I automated by adding the following line in /etc/udev/rules.d/99-huawei-modem.rules
:
ACTION=="add", ATTR{idVendor}=="12d1", ATTR{idProduct}=="1f1c", RUN+="usb_modeswitch -v 12d1 -p 1f1c -W -I -M 55534243123456780000000000000011062000000101000100000000000000"
This worked every time without resetting the USB bus…
However, while trying to reproduce these issues while writing this post, it just worked every time, even with the normal switching logic…:
USB_ModeSwitch log from Sun Dec 23 09:01:16 GMT 2018
Use global config file: /etc/usb_modeswitch.conf
Adjust delay for USB storage devices ...
Delay set to 4 seconds
Raw parameters: {--switch-mode} {1-1.1.3:1.0}
Use top device dir /sys/bus/usb/devices/1-1.1.3
Check class of first interface ...
Interface 0 class is 08.
----------------
USB values from sysfs:
manufacturer Vodafone(Huawei)
product HUAWEI Mobile
serial FFFFFFFFFFFFFFFF
----------------
Found packed config collection /usr/share/usb_modeswitch/configPack.tar.gz
ConfigList: pack/12d1:1f1c pack/12d1:#linux
SCSI attributes not needed, move on
Check config: pack/12d1:1f1c
! matched. Read config data
Extract config 12d1:1f1c from collection /usr/share/usb_modeswitch/configPack.tar.gz
Command line:
usb_modeswitch -W -D -b 1 -g 5 -v 12d1 -p 1f1c -f $flags(config)
Verbose debug output of usb_modeswitch and libusb follows
(Note that some USB errors are to be expected in the process)
--------------------------------
Read long config from command line
* usb_modeswitch: handle USB devices with multiple modes
* Version 2.5.0 (C) Josua Dietze 2017
* Based on libusb1/libusbx
! PLEASE REPORT NEW CONFIGURATIONS !
DefaultVendor= 0x12d1
DefaultProduct= 0x1f1c
TargetVendor= 0x12d1
TargetProductList="157a,1590"
HuaweiNewMode=1
System integration mode enabled
Use given bus/device number: 001/005 ...
Look for default devices ...
bus/device number matched
found USB ID 12d1:1f1c
vendor ID matched
product ID matched
Found devices in default mode (1)
Get the current device configuration ...
Current configuration number is 1
Use interface number 0
with class 8
Use endpoints 0x01 (out) and 0x81 (in)
USB description data (for identification)
-------------------------
Manufacturer: Vodafone(Huawei)
Product: HUAWEI Mobile
Serial No.: FFFFFFFFFFFFFFFF
-------------------------
Using standard Huawei switching message
Looking for active driver ...
OK, driver detached
Set up interface 0
Use endpoint 0x01 for message sending ...
Trying to send message 1 to endpoint 0x01 ...
OK, message successfully sent
Read the response to message 1 (CSW) ...
Response successfully read (13 bytes), status 0
Reset response endpoint 0x81
Reset message endpoint 0x01
ok:busdev
--------------------------------
(end of usb_modeswitch output)
Check success of mode switch for max. 20 seconds ...
Wait for device file system (1 sec.) ...
Read attributes ...
All attributes matched
Mode switching was successful, found 12d1:1590 (Vodafone(Huawei): HUAWEI Mobile)
Logger is /usr/bin/logger
Check for AVOID_RESET_QUIRK kernel attribute
AVOID_RESET_QUIRK activated
All done, exit
Dialing out
Once switched, the modem presents itself as a network interface using the cdc_ether
driver, including a DHCP-server. So this usually yields a fully functional network connection, fully automatically.
My SIM-card was PIN-locked. In that case, the modem redirects all HTTP-requests to its web-based interface, and asks for the PIN.
I wanted to automate this step. Which seems to be a bit more complicated than expected. I sniffed a promising call to /api/pin/operate
with an XML-body:
0 1234 302258
But I couldn’t reproduces this using Curl. Apparently, the token
attribute is not constant. So where does that come from? Luckily, the JavaScript used in the web-interface is not obscured, so it’s fairly easy to find out that it’s a variable set in /html/js/vendor.js
, and is different each time. These steps seem to work:
PIN="1234"
TOKEN="$( curl --silent --connect-timeout 5 http://192.168.9.1/html/js/vendor.js | grep 'var STR_AJAX_VALUE' | sed 's/.*"\([0-9]*\)".*/\1/' )"
curl -v http://192.168.9.1/api/pin/operate -d "0 ${PIN} ${TOKEN} "