Query and change the USB composition of Sierra Wireless cellular modems when the device is stuck in MBIM-only mode and no AT or QMI serial ports are available.
Originally written by Bjørn Mork (2015, GPLv2) as a one-off hack. The same functionality is now built into qmicli via --dms-swi-get-usb-composition and --dms-swi-set-usb-composition, but this standalone Perl script remains useful on minimal systems or when you want a single file with no build dependencies beyond Perl modules.
- The Problem
- What This Script Does
- Supported Hardware
- Requirements
- Installation
- Usage on Linux
- Usage on Android (via ADB)
- USB Composition Reference
- How It Works
- After Changing Composition
- Troubleshooting
- Alternatives
- References
- License
Sierra Wireless (now Semtech) Qualcomm-based modems expose multiple USB interfaces to the host. Which interfaces appear — diagnostic (DM), NMEA/GPS, AT command port, QMI, MBIM, RMNET, ECM, etc. — is controlled by a setting called USB composition.
Common scenarios where composition becomes a problem on Linux:
| Scenario | Symptom |
|---|---|
| Windows driver or Sierra application changed composition | Modem enumerates as MBIM-only; no /dev/ttyUSB* or AT port |
| Firmware defaults to composition 9 (MBIM only) | NetworkManager/ModemManager works, but qmicli and AT tools cannot run |
| MBIM-only PID lock (some EM7455/MC7455 units) | Even after reset, only /dev/cdc-wdm0 appears |
Normally you would fix this with AT commands:
AT!USBCOMP=1,1,100D # newer MC/EM series (bitmask style)
AT!UDUSBCOMP=8 # older modules (index style)
AT!RESET
But if the modem is in MBIM-only mode, there is no AT port. The QMI control channel is also gone. What remains is the MBIM control device (/dev/cdc-wdm*), driven by the Linux cdc_mbim kernel driver.
This script reaches the modem through that MBIM channel by tunneling QMI messages inside a vendor-specific MBIM service — the same mechanism later adopted by libqmi's --device-open-mbim mode.
- Opens an MBIM control device (default
/dev/cdc-wdm0) and verifies it is served by thecdc_mbimdriver. - Establishes an MBIM session (OPEN / OPEN_DONE).
- Tunnels QMI over MBIM using the Qualcomm EXT_QMUX vendor service.
- Allocates a QMI DMS (Device Management Service) client ID.
- Sends QMI_DMS_SWI_GET_USB_COMPOSITION (
0x555B) to read the current composition and the list of supported compositions. - Optionally sends QMI_DMS_SWI_SET_USB_COMPOSITION (
0x555C) to change the composition. - Cleans up (releases DMS client, MBIM CLOSE) and exits.
Without --usbcomp, the script only displays the current and supported compositions. With --usbcomp=N, it attempts to switch to composition N if the modem reports it as supported.
Tested and documented primarily on Qualcomm-based Sierra Wireless modules in the MC/EM series, including:
- MC7354, MC7710, MC7455
- EM7455, EM7565
- Similar modules exposing MBIM via
cdc_mbim
The script requires:
- An MBIM control character device (
/dev/cdc-wdm*) - The Qualcomm EXT_QMUX MBIM vendor service (UUID
d1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3)
You can verify the QMI-over-MBIM service is present:
mbimcli -d /dev/cdc-wdm0 --query-device-servicesLook for a service named qmi with the UUID above and CID msg (1).
Not supported: modems that only expose a QMI (qmi_wwan) interface with no MBIM channel, or modems lacking the EXT_QMUX service.
- Linux with the
cdc_mbimkernel driver - Root privileges (the
/dev/cdc-wdm*device is typically restricted) - Perl 5 with these modules:
UUID::TinyIPC::ShareableJSONGetopt::Long(core)Fcntl(core)sys/ioctl.ph(fromperl-base/perl-devon Debian/Ubuntu)
mbimcli— from libmbimqmicli— from libqmimmcli— from ModemManager
git clone https://github.com/mavstuff/swi_setusbcomp.git
cd swi_setusbcomp
chmod +x scripts_swi_setusbcomp.plOr download the script directly from the original upstream.
Pick the instructions for your distribution. All examples assume you run the script as root (or with sudo).
sudo apt update
sudo apt install perl libuuid-tiny-perl libipc-shareable-perl libjson-perlEnsure MBIM kernel support is present, then install Perl and core modules:
opkg update
opkg install perl perlbase-essential perlbase-json-pp perlbase-getopt perlbase-fcntl \
kmod-usb-net-cdc-mbimUUID::Tiny and IPC::Shareable are not packaged in the default OpenWrt feeds. Options:
- Entware (recommended on routers with USB storage) — see below
- cpanminus on device — needs extra flash/RAM and build tools:
opkg install perlbase-extutils perlbase-io make gcc
curl -L https://cpanmin.us | perl - --notest UUID::Tiny IPC::ShareableCopy the script to the router (scp) or download it with wget if space is tight.
Entware provides opkg packages for embedded devices with more storage than stock OpenWrt.
/opt/bin/opkg update
/opt/bin/opkg install perl perl-jsonInstall the remaining modules via CPAN (see Entware Perl wiki):
/opt/bin/opkg install perl-dev make gcc
/opt/bin/perl -MCPAN -e 'install UUID::Tiny'
/opt/bin/perl -MCPAN -e 'install IPC::Shareable'Run the script with /opt/bin/perl scripts_swi_setusbcomp.pl.
Common in containers, industrial gateways, and minimal embedded images:
apk add perl perl-json perl-dev make gcc musl-dev
cpanm --notest UUID::Tiny IPC::ShareableAlpine ships perl-json but not perl-uuid-tiny or perl-ipc-shareable; cpanm (from perl-app-cpanminus) is the shortest path.
Most modules are in the official extra repository; UUID::Tiny is AUR-only.
sudo pacman -S perl perl-json perl-ipc-shareable cpanminus
cpanm --notest UUID::TinyOr install perl-uuid-tiny from the AUR (yay -S perl-uuid-tiny / paru -S perl-uuid-tiny).
CentOS 7 / RHEL 7 — enable EPEL first, then install:
sudo yum install epel-release
sudo yum install perl perl-JSON perl-UUID-Tiny perl-IPC-ShareableIf yum cannot find a module, use CPAN:
sudo yum install perl-CPAN gcc make
sudo cpan UUID::Tiny IPC::ShareableCentOS Stream 8/9, Rocky 8/9, AlmaLinux 8/9, RHEL 8/9 — dnf can resolve packages by Perl module name:
sudo dnf install perl 'perl(UUID::Tiny)' 'perl(IPC::Shareable)' 'perl(JSON)'Enable EPEL on RHEL if packages are missing: sudo dnf install epel-release.
sudo dnf install perl 'perl(UUID::Tiny)' 'perl(IPC::Shareable)' 'perl(JSON)'sudo zypper refresh
sudo zypper install perl perl-JSON perl-UUID-Tiny perl-IPC-ShareableSearch if a package name differs on your release: zypper search perl-IPC-Shareable.
Base SLES images ship a minimal Perl set. Enable SUSE Package Hub for community packages:
sudo SUSEConnect -p sle-packagehub/<version>/x86_64 # adjust version/arch
sudo zypper refresh
sudo zypper install perl perl-JSON perl-UUID-Tinyperl-IPC-Shareable may not be in Package Hub for every SLES release. If zypper install perl-IPC-Shareable fails:
sudo zypper install perl-CPAN gcc make
sudo cpan IPC::ShareableAlternatively, add the devel:languages:perl OBS repository for your SLE service pack (see software.opensuse.org).
These build systems do not share one package manager. Typical approaches:
- Yocto: add
perl,perl-module-json, and CPAN recipes (or a custom layer) forUUID::TinyandIPC::Shareableto your image; ensurecdc_mbimis enabled in the kernel config (CONFIG_USB_NET_CDC_MBIM). - Buildroot: enable
BR2_PACKAGE_PERLand required Perl module packages inmenuconfig, or install modules at first boot withcpanmif your rootfs is writable. - Prebuilt vendor SDK: many LTE gateway BSPs ship Debian- or OpenWrt-based rootfs — use the matching section above.
If Perl module installation on the target is impractical, run the script from a chroot or NFS root with a fuller userspace, or use qmicli --device-open-mbim from a cross-built libqmi instead.
scripts_swi_setusbcomp.pl [options]
| Option | Description |
|---|---|
--device=<path> |
MBIM control device (default: /dev/cdc-wdm0) |
--usbcomp=<num> |
Change USB composition to index <num> |
--verbose |
Print QMI subsystem versions and composition table (default: on) |
--noverbose |
Suppress extra output |
--debug |
Hex-dump all MBIM/QMI traffic and decode message headers |
--help |
Show usage and exit |
Show current composition and all supported modes (read-only):
sudo ./scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0Example output:
MBIM OPEN succeeded
MBIM QMI support verified
supports 12 QMI subsystems:
QMI_CTL (1.0)
QMI_WDS (1.67)
...
Got QMI DMS client ID '1'
Current USB composition: 9
USB compositions:
6 - DM NMEA AT QMI SUPPORTED
8 - DM NMEA AT MBIM SUPPORTED
* 9 - MBIM SUPPORTED
Switch from MBIM-only (9) to DM+NMEA+AT+QMI (6) — common fix for MC7354:
sudo ./scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0 --usbcomp=6Switch to DM+NMEA+AT+MBIM (8) — keeps MBIM networking while restoring AT/QMI ports:
sudo ./scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0 --usbcomp=8Debug a failing session:
sudo ./scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0 --debugAfter any composition change, reset or power-cycle the modem for the new USB layout to take effect (see After Changing Composition).
This script can run on Android when the device exposes a Linux-style /dev/cdc-wdm* node backed by the cdc_mbim kernel driver. That is common on embedded/industrial Android boards and rooted setups with an external USB LTE modem (OTG), but not on typical consumer phones where the built-in modem is managed by the proprietary RIL stack.
| Setup | Likely to work? |
|---|---|
| AOSP / industrial tablet with USB or M.2 LTE modem | Yes, if cdc_mbim is in the kernel and ueventd creates /dev/cdc-wdm* |
| Rooted phone/tablet + USB LTE dongle (OTG) | Yes, with root and kernel driver support |
| Stock phone, internal modem only | No — no /dev/cdc-wdm0 for userspace; use a PC instead |
| Termux without root | No — this script opens /dev/cdc-wdm0 directly; termux-usb cannot substitute without code changes |
The script is not Termux-USB compatible as-is: it expects a standard character device node, not an Android USB file descriptor.
On the host PC:
- Android platform-tools (
adb) - USB debugging enabled on the Android device
On the Android device:
- Root (
su) or an eng/userdebug build withadb root— required to open/dev/cdc-wdm* - Kernel modules
cdc_mbimandcdc_wdm(check withlsmodorcat /proc/modules) - Perl 5 with
UUID::Tiny,IPC::Shareable, andJSON— Termux is the practical way to get these on Android
From your PC:
adb devices
adb shell getprop ro.build.type # userdebug/eng helps for adb rootCheck that the MBIM control device exists:
adb shell su -c 'ls -l /dev/cdc-wdm*'
adb shell su -c 'readlink -f /sys/class/usbmisc/cdc-wdm0/device/driver'The driver symlink should end in cdc_mbim. If you see /sys/class/usbmisc/cdc-wdm0 in sysfs but no /dev/cdc-wdm0, the Android ueventd rules are missing. Add to ueventd.rc (custom ROM / vendor image):
/dev/cdc-wdm* 0660 radio radio
See cdc-wdm support on AOSP and vendor guides (e.g. Quectel Android RIL).
Confirm the modem enumerates:
adb shell su -c 'lsusb'
adb shell su -c 'dmesg | grep -i mbim'Install Termux on the device (GitHub build recommended if you need run-as from adb). Inside Termux:
pkg update
pkg install perl make clang
curl -L https://cpanmin.us | perl - App::cpanminus
cpanm --notest UUID::Tiny IPC::Shareable JSONTo drive Termux from adb without typing on the device (rooted, or debuggable Termux build):
adb shell -tt run-as com.termux \
files/usr/bin/env \
PATH=/data/data/com.termux/files/usr/bin \
LD_PRELOAD=/data/data/com.termux/files/usr/lib/libtermux-exec.so \
HOME=/data/data/com.termux/files/home \
bash -lic 'cpanm --notest UUID::Tiny IPC::Shareable JSON'From your PC, in the repository directory:
adb push scripts_swi_setusbcomp.pl /data/local/tmp/
adb shell su -c 'chmod 755 /data/local/tmp/scripts_swi_setusbcomp.pl'The RIL daemon (rild) may hold the modem open. Stop it before running the script (embedded devices only — this disables cellular on the device until reboot):
adb root # eng/userdebug builds only
adb shell stop ril-daemon
# or, with su:
adb shell su -c 'setprop ctl.stop ril-daemon'On SELinux-enforced builds you may need permissive mode for testing:
adb shell su -c 'setenforce 0'Using Termux's Perl as root (typical path):
# Query current composition
adb shell su -c '/data/data/com.termux/files/usr/bin/perl /data/local/tmp/scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0'
# Change composition (example: switch to DM+NMEA+AT+MBIM)
adb shell su -c '/data/data/com.termux/files/usr/bin/perl /data/local/tmp/scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0 --usbcomp=8'If Perl is installed elsewhere, adjust the path. Debug output:
adb shell su -c '/data/data/com.termux/files/usr/bin/perl /data/local/tmp/scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0 --debug'Power-cycle the USB modem or reset via AT/QMI if available, then reboot the Android device or restart rild:
adb shell su -c 'setprop ctl.start ril-daemon'
adb rebootVerify the new composition after re-enumeration:
adb shell su -c '/data/data/com.termux/files/usr/bin/perl /data/local/tmp/scripts_swi_setusbcomp.pl --device=/dev/cdc-wdm0'| Symptom | What to try |
|---|---|
Permission denied on /dev/cdc-wdm0 |
Run via su; check node owner (radio group) and SELinux context (radio_device) |
No /dev/cdc-wdm0 node |
Fix ueventd.rc; confirm cdc_mbim bound in dmesg |
only MBIM devices are supported |
Driver is qmi_wwan, not cdc_mbim — wrong USB composition or driver |
Failed to verify QMI vendor specific MBIM service |
Modem firmware lacks EXT_QMUX over MBIM; try mbimcli --query-device-services via cross-built libmbim |
open /dev/cdc-wdm0: Device or resource busy |
Stop rild, ModemManager-like services, or other apps using the modem |
Termux Perl not found from su |
Use the full path /data/data/com.termux/files/usr/bin/perl (shown above) |
run-as com.termux fails |
Install the debuggable Termux APK from GitHub releases, or use root |
If you can move the modem to a Linux PC (or the Android device runs a Linux chroot/VM with USB passthrough), use qmicli there instead:
qmicli -d /dev/cdc-wdm0 --device-open-mbim --dms-swi-get-usb-composition
qmicli -d /dev/cdc-wdm0 --device-open-mbim --dms-swi-set-usb-composition=8The script embeds Sierra Wireless composition indices 0–22. The modem firmware reports which indices are actually supported; unsupported ones are marked NOT SUPPORTED in the listing.
| Index | Interfaces | Typical use |
|---|---|---|
| 0 | HIP, DM, NMEA, AT, MDM1, MDM2, MDM3, MS | Legacy |
| 1 | HIP, DM, NMEA, AT, MDM1, MS | Legacy |
| 2 | HIP, DM, NMEA, AT, NIC1, MS | Legacy |
| 3 | HIP, DM, NMEA, AT, MDM1, NIC1, MS | Legacy |
| 4 | HIP, DM, NMEA, AT, NIC1, NIC2, NIC3, MS | Legacy |
| 5 | HIP, DM, NMEA, AT, ECM1, MS | Legacy ECM |
| 6 | DM, NMEA, AT, QMI | Classic Linux QMI setup |
| 7 | DM, NMEA, AT, RMNET1, RMNET2, RMNET3 | RMNET data channels |
| 8 | DM, NMEA, AT, MBIM | MBIM + management ports (recommended on modern kernels) |
| 9 | MBIM | MBIM-only (problematic if you need AT/QMI) |
| 10 | NMEA, MBIM | GPS + MBIM |
| 11 | DM, MBIM | Diagnostic + MBIM |
| 12 | DM, NMEA, MBIM | Diagnostic + GPS + MBIM |
| 13–22 | Dual-config (comp6/comp7 paired with comp8–comp12) | Boot-time composition switching |
| Abbr | Meaning |
|---|---|
| HIP | Host Interface Protocol (legacy Sierra) |
| DM | Diagnostic / DM port (/dev/ttyUSB*, often first port) |
| NMEA | GPS NMEA output |
| AT | AT command port |
| MDM1–3 | Modem network interfaces (QMI/WWAN) |
| NIC1–3 | Network interface (CDC-NCM/ECM) |
| ECM1 | CDC-ECM network |
| QMI | QMI control (/dev/cdc-wdm* via qmi_wwan) |
| MBIM | MBIM control + data (/dev/cdc-wdm* + wwan0 via cdc_mbim) |
| RMNET1–3 | Qualcomm RMNET data channels |
| MS | Mass Storage (firmware update mode) |
On newer firmware (MC7455/EM7455 and later), Sierra also documents compositions via bitmask with AT!USBCOMP. The numeric indices above (6, 8, 9, etc.) map to the same logical layouts but use the older index scheme understood by the QMI vendor commands.
┌─────────────┐ MBIM protocol ┌──────────────────┐
│ Script │ ◄──────────────────► │ /dev/cdc-wdm0 │
│ (parent) │ read/write │ (cdc_mbim drv) │
└──────┬──────┘ └────────┬─────────┘
│ fork() │
┌──────▼──────┐ │
│ Reader │ ◄── async MBIM responses ─────┘
│ (child) │
└──────┬──────┘
│ IPC::Shareable
▼
lastmbim / lastqmi (shared between processes)
The parent process writes MBIM requests; a forked child continuously reads responses and decodes them into shared memory. This avoids blocking on bidirectional I/O on a single file descriptor.
The script implements a minimal MBIM client:
- OPEN / OPEN_DONE — negotiate
MaxControlTransfersize (default 4096 bytes, overridden byIOCTL_WDM_MAX_COMMANDioctl when available) - COMMAND / COMMAND_DONE — send service requests
- CLOSE / CLOSE_DONE — tear down session
Qualcomm defines a vendor MBIM service that carries raw QMUX/QMI payloads:
| Property | Value |
|---|---|
| Service name | EXT_QMUX |
| UUID | d1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3 |
| CID | 1 |
| Operation | MBIM Set (type=1) with QMUX buffer as InformationBuffer |
| Response | Raw QMUX response in COMMAND_DONE InformationBuffer |
This tunnel is documented in the libmbim MBIM protocol notes and was reverse-engineered for the MC7710 by Bjørn Mork (Sierra Wireless forum discussion).
| Message | ID | Direction | TLVs |
|---|---|---|---|
| SWI Get USB Composition | 0x555B |
Request: none | Response: 0x10 = current (uint8), 0x11 = supported list (count + uint8[]) |
| SWI Set USB Composition | 0x555C |
Request: 0x01 = new index (uint8) |
Response: standard QMI result TLV 0x02 |
| SWI Set FCC Authentication | 0x555F |
(referenced in code comments, not implemented) | — |
These messages were added to libqmi in October 2017 (libqmi-devel patch), vendor ID 0x1199 (Sierra Wireless).
- Validate
$deviceis a character device withcdc_mbimdriver. fork()— child runsread_mbim()loop until CLOSE_DONE.- Parent sends MBIM OPEN, waits for OPEN_DONE (
0x80000001). - Parent sends QMI_CTL GET_VERSION_INFO (
0x0021) via EXT_QMUX to verify tunnel works. - Parent sends QMI_CTL GET_CLIENT_ID (
0x0022, TLV0x01= DMS service) → stores DMS CID. - Parent sends DMS SWI GET USB COMPOSITION (
0x555B) → prints current + supported list. - If
--usbcomp=Ngiven and valid: sends DMS SWI SET USB COMPOSITION (0x555C, TLV0x01= N). quit(): releases DMS CID (0x0023), sends MBIM CLOSE, waits for child exit.
A composition change is stored in modem NVRAM but does not re-enumerate USB interfaces immediately. You must reset the module:
sudo qmicli -d /dev/cdc-wdm0 --dms-set-operating-mode=offline
sudo qmicli -d /dev/cdc-wdm0 --dms-set-operating-mode=resetsudo mmcli -m 0 --reset- USB disconnect/reconnect
- Module power cycle
- Laptop embedded modem hard reset
After reset, verify with lsusb, mmcli -L, or run the script again without --usbcomp to confirm the new composition is active. You should see additional /dev/ttyUSB* ports and/or a qmi_wwan interface depending on the chosen mode.
The MBIM device node does not exist. Check ls -l /dev/cdc-wdm* and dmesg for USB enumeration errors. The modem may be in QDL/firmware-download mode (PID 0x9070) instead of runtime mode.
The device is not handled by cdc_mbim — it may be in QMI mode (qmi_wwan) or serial-only mode. In that case you can use qmicli or AT commands directly and do not need this script.
The modem's MBIM stack does not expose EXT_QMUX. Run mbimcli -d /dev/cdc-wdm0 --query-device-services to confirm. Some non-Qualcomm or heavily locked firmware builds omit this service.
The firmware only allows a subset of compositions (often 6, 8, and 9 on MC7455/EM7455). The script refuses to send an unsupported value. Use the read-only listing to see what your module reports.
The QMI request returned an error. Run with --debug to inspect the raw response. Common causes: modem busy (stop NetworkManager/ModemManager first), insufficient permissions, or firmware restrictions.
Some units have USB PID–locked compositions stored in NV memory. If qmicli --dms-swi-set-usb-composition and this script both succeed but the modem returns to MBIM-only, the module may need an NVU profile change. See MC7455 stuck in MBIM-only USB composition for advanced recovery (custom NVU files, parsecwe.pl, etc.).
sudo systemctl stop ModemManager
# run script
sudo systemctl start ModemManagerSince libqmi 1.20, the officially supported approach:
# Query (works in MBIM mode)
sudo qmicli -d /dev/cdc-wdm0 --device-open-mbim --dms-swi-get-usb-composition
# Set
sudo qmicli -d /dev/cdc-wdm0 --device-open-mbim --dms-swi-set-usb-composition=8
# Reset
sudo qmicli -d /dev/cdc-wdm0 --device-open-mbim --dms-set-operating-mode=resetWith a recent libqmi, --device-open-mbim is often the default when the device is MBIM-capable.
AT!ENTERCND="A710" # unlock protected commands (password varies by module)
AT!UDUSBCOMP=? # list compositions (older modules)
AT!UDUSBCOMP=8 # set composition 8
AT!USBCOMP=? # list compositions (MC7455/EM7455+)
AT!USBCOMP=1,1,100D # set via bitmask (diag,nmea,modem,mbim)
AT!RESET
- SWI Connect / Skylight (Windows) — can change composition via GUI
- Sierra Linux QMI SDK — documents
USBCompParams()/USBCompConfig()vendor APIs
Bjørn Mork described this script as a "simple run-once hack" that was "never intended to be a tool", and wrote: "I don't recommend it ;-)". It has nonetheless proven invaluable for recovering modems stranded in MBIM-only mode and served as the reference implementation for the libqmi vendor commands.
Script: GPLv2 — Copyright (c) 2015 Bjørn Mork <bjorn@mork.no>
Documentation: GPLv2 — Copyright (c) 2026 Artem Moroz <artem.moroz@gmail.com>