#!/usr/bin/perl # Copyright (c) 2015 Bjørn Mork # GPLv2 use strict; use warnings; use Getopt::Long; use UUID::Tiny ':std'; use IPC::Shareable; use Fcntl ':mode'; use File::Basename; use Time::HiRes qw (sleep); my $maxctrl = 4096; # default, will be overridden by ioctl if supported my $mgmt = "/dev/cdc-wdm0"; my $reset; my $usbreset; my $debug; my $verbose = 1; my $usbcomp; # a few global variables my $msgs; my $dmscid; my $tid = 1; # defaulting to MBIM mode my $mbim = 1; GetOptions( 'usbcomp=i' => \$usbcomp, 'device=s' => \$mgmt, 'reset!' => \$reset, 'usbreset!' => \$usbreset, 'debug!' => \$debug, 'verbose!' => \$verbose, 'help|h|?' => \&usage, ) || &usage; ### MBIM helpers ### sub _push { my ($buf, $format, @vars) = @_; my $add = pack($format, @vars); $buf .= $add; # update length my $len = unpack("V", substr($buf, 4, 4)); $len += length($add); substr($buf, 4, 4) = pack("V", $len); return $buf; } sub _pop { my ($buf, $format, @vars) = @_; (@vars) = unpack($format, $buf); my $x = pack($format, @vars); return $buf .= pack($format, @vars); } my %msg = ( # Table 9‐3: Control messages sent from the host to the function 'MBIM_OPEN_MSG' => 1, 'MBIM_CLOSE_MSG' => 2, 'MBIM_COMMAND_MSG' => 3, 'MBIM_HOST_ERROR_MSG' => 4, # Table 9‐9: Control Messages sent from function to host 'MBIM_OPEN_DONE' => 0x80000001, 'MBIM_CLOSE_DONE' => 0x80000002, 'MBIM_COMMAND_DONE' => 0x80000003, 'MBIM_FUNCTION_ERROR_MSG' => 0x80000004, 'MBIM_INDICATE_STATUS_MSG' => 0x80000007, ); # Table 10‐3: Services Defined by MBIM my %uuid = ( UUID_BASIC_CONNECT => 'a289cc33-bcbb-8b4f-b6b0-133ec2aae6df', UUID_SMS => '533fbeeb-14fe-4467-9f90-33a223e56c3f', UUID_USSD => 'e550a0c8-5e82-479e-82f7-10abf4c3351f', UUID_PHONEBOOK => '4bf38476-1e6a-41db-b1d8-bed289c25bdb', UUID_STK => 'd8f20131-fcb5-4e17-8602-d6ed3816164c', UUID_AUTH => '1d2b5ff7-0aa1-48b2-aa52-50f15767174e', UUID_DSS => 'c08a26dd-7718-4382-8482-6e0d583c4d0e', # "well known" vendor specific services UUID_EXT_QMUX => 'd1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3', # ref unknown... UUID_MULTICARRIER => '8b569648-628d-4653-9b9f-1025404424e1', # ref http://feishare.com/attachments/article/252/implementing-multimode-multicarrier-devices.pdf UUID_MSFWID => 'e9f7dea2-feaf-4009-93ce-90a3694103b6', # http://msdn.microsoft.com/en-us/library/windows/hardware/jj248721.aspx UUID_MS_HOSTSHUTDOWN => '883b7c26-985f-43fa-9804-27d7fb80959c', # http://msdn.microsoft.com/en-us/library/windows/hardware/jj248720.aspx ); sub uuid_to_service { my $uuid = shift; my ($service) = grep { $uuid{$_} eq $uuid } keys %uuid; return 'UNKNOWN' unless $service; $service =~ s/^UUID_//; return $service; } # MBIM_MESSAGE_HEADER sub init_msg_header { my $type = shift; return &_push('', "VVV", $type, 0, $tid++); } # MBIM_FRAGMENT_HEADER sub push_fragment_header { my ($buf, $total, $current) = @_; return $buf = &_push($buf, "VV", $total, $current); } # MBIM_OPEN_MSG sub mk_open_msg { my $buf = &init_msg_header(1); # MBIM_OPEN_MSG $buf = &_push($buf, "V", $maxctrl); # MaxControlTransfer printf "MBIM>: " . "%02x " x length($buf) . "\n", unpack("C*", $buf) if $debug; return $buf; } # MBIM_CLOSE_MSG sub mk_close_msg { my $buf = &init_msg_header(2); # MBIM_CLOSE_MSG printf "MBIM>: " . "%02x " x length($buf) . "\n", unpack("C*", $buf) if $debug; return $buf; } # MBIM_COMMAND_MSG sub mk_command_msg { my ($service, $cid, $type, $info) = @_; my $uuid = string_to_uuid($uuid{"UUID_$service"} || $service) || return ''; my $buf = &init_msg_header(3); # MBIM_COMMAND_MSG $buf = &push_fragment_header($buf, 1, 0); $uuid =~ tr/-//d; $buf = &_push($buf, "a*", $uuid); # DeviceServiceId $buf = &_push($buf, "VVV", $cid, # CID $type, # 0 for a query operation, 1 for a Set operation. length($info), # InformationBufferLength ); $buf = &_push($buf, "a*", $info); # InformationBuffer printf "MBIM>: " . "%02x " x length($buf) . "\n", unpack("C*", $buf) if $debug; return $buf; } sub decode_mbim { my $msg = shift; my ($type, $len, $tid) = unpack("VVV", $msg); if ($debug) { print "MBIM_MESSAGE_HEADER\n"; printf " MessageType:\t0x%08x\n", $type; printf " MessageLength:\t%d\n", $len; printf " TransactionId:\t%d\n", $tid; } if ($type == 0x80000001 || $type == 0x80000002) { # MBIM_OPEN_DONE || MBIM_CLOSE_DONE my $status = unpack("V", substr($msg, 12)); printf " Status:\t0x%08x\n", $status if $debug; # save message type push(@$msgs, { status => $type, index => scalar @$msgs, }); } elsif ($type == 0x80000003) { # MBIM_COMMAND_DONE my ($total, $current) = unpack("VV", substr($msg, 12)); # FragmentHeader if ($debug) { print "MBIM_FRAGMENT_HEADER\n"; printf " TotalFragments:\t0x%08x\n", $total; printf " CurrentFragment:\t0x%08x\n", $current; } my $uuid = uuid_to_string(substr($msg, 20, 16)); my $service = &uuid_to_service($uuid); print "$service ($uuid)\n" if $debug; my ($cid, $status, $infolen) = unpack("VVV", substr($msg, 36)); my $info = substr($msg, 48); if ($debug) { printf " CID:\t\t0x%08x\n", $cid; printf " Status:\t0x%08x\n", $status; print "InformationBuffer [$infolen]:\n"; } if ($infolen != length($info)) { print "Fragmented MBIM transactions are not supported\n"; } elsif ($service eq "EXT_QMUX") { # save the decoded QMI message my $lastqmi = &decode_qmi($info); # save message push(@$msgs, { status => 0, index => scalar @$msgs, qmi => $lastqmi}) if $lastqmi; } # silently ignoring InformationBuffer payload of other services } # ignoring all other types of MBIM messages } # read from F until timeout sub reader { my $timeout = shift || 0; eval { local $SIG{ALRM} = sub { die "timeout\n" }; local $SIG{TERM} = sub { die "close\n" }; my $raw = ''; my $msglen = 0; alarm $timeout; do { my $len = 0; if ($len < 3 || $len < $msglen) { my $tmp; my $n = sysread(F, $tmp, $maxctrl); if ($n) { $len = $n; $raw = $tmp; printf "%s<: " . "%02x " x $n . "\n", $mbim ? "MBIM" : "QMI", unpack("C*", $tmp) if $debug; } else { die "eof\n"; } } # get expected message length if ($mbim) { $msglen = unpack("V", substr($raw, 4, 4)); } else { $msglen = unpack("v", substr($raw, 1, 2)) + 1; } if ($len >= $msglen) { $len -= $msglen; if ($mbim) { &decode_mbim(substr($raw, 0, $msglen)); die "close\n" if (grep { $_->{status} == 0x80000002 } @$msgs); # exit on CLOSE_DONE } else { my $lastqmi = &decode_qmi(substr($raw, 0, $msglen)); push(@$msgs, { status => 0, index => scalar @$msgs, qmi => $lastqmi}) if $lastqmi; } $raw = substr($raw, $msglen); $msglen = 0; } else { warn "$len < $msglen\n"; } } while (1); alarm 0; }; if ($@) { die unless $@ =~ /^close/; # propagate unexpected errors } } ### QMI helpers ### my %sysname = ( 0x00 => "QMI_CTL", # Control service 0x01 => "QMI_WDS", # Wireless data service 0x02 => "QMI_DMS", # Device management service 0x03 => "QMI_NAS", # Network access service 0x04 => "QMI_QOS", # Quality of service, err, service 0x05 => "QMI_WMS", # Wireless messaging service 0x06 => "QMI_PDS", # Position determination service 0x07 => "QMI_AUTH", # Authentication service 0x08 => "QMI_AT", # AT command processor service 0x09 => "QMI_VOICE", # Voice service 0x0a => "QMI_CAT2", # Card application toolkit service (new) 0x0b => "QMI_UIM", # UIM service 0x0c => "QMI_PBM", # Phonebook service 0x0d => "QMI_QCHAT", # QCHAT Service 0x0e => "QMI_RMTFS", # Remote file system service 0x0f => "QMI_TEST", # Test service 0x10 => "QMI_LOC", # Location service 0x11 => "QMI_SAR", # Specific absorption rate service 0x12 => "QMI_IMSS", # IMS settings service 0x13 => "QMI_ADC", # Analog to digital converter driver service 0x14 => "QMI_CSD", # Core sound driver service 0x15 => "QMI_MFS", # Modem embedded file system service 0x16 => "QMI_TIME", # Time service 0x17 => "QMI_TS", # Thermal sensors service 0x18 => "QMI_TMD", # Thermal mitigation device service 0x19 => "QMI_SAP", # Service access proxy service 0x1a => "QMI_WDA", # Wireless data administrative service 0x1b => "QMI_TSYNC", # TSYNC control service 0x1c => "QMI_RFSA", # Remote file system access service 0x1d => "QMI_CSVT", # Circuit switched videotelephony service 0x1e => "QMI_QCMAP", # Qualcomm mobile access point service 0x1f => "QMI_IMSP", # IMS presence service 0x20 => "QMI_IMSVT", # IMS videotelephony service 0x21 => "QMI_IMSA", # IMS application service 0x22 => "QMI_COEX", # Coexistence service 0x23 => "QMI_RESERVED_35", # Reserved 0x24 => "QMI_PDC", # Persistent device configuration service 0x25 => "QMI_RESERVED_37", # Reserved 0x26 => "QMI_STX", # Simultaneous transmit service 0x27 => "QMI_BIT", # Bearer independent transport service 0x28 => "QMI_IMSRTP", # IMS RTP service 0x29 => "QMI_RFRPE", # RF radiated performance enhancement service 0x2a => "QMI_DSD", # Data system determination service 0x2b => "QMI_SSCTL", # Subsystem control service 0xe0 => "QMI_CAT", # Card application toolkit service 0xe1 => "QMI_RMS", # Remote management service ); # dumped from GobiAPI_2013-07-31-1347/GobiConnectionMgmt/GobiConnectionMgmtAPIEnums.h # using # perl -e 'while (<>){ if (m!eQMI_SVC_([^,]*),\s*//\s*(\d+)\s(.*)!) { my $svc = $1; $svc = "CTL" if ($svc eq "CONTROL"); my $num = $2; my $descr = $3; printf "\t0x%02x => \"$descr\",\n", $num; } }' < /tmp/xx my %sysdescr = ( 0x00 => "Control service", 0x01 => "Wireless data service", 0x02 => "Device management service", 0x03 => "Network access service", 0x04 => "Quality of service, err, service ", 0x05 => "Wireless messaging service", 0x06 => "Position determination service", 0x07 => "Authentication service", 0x08 => "AT command processor service", 0x09 => "Voice service", 0x0a => "Card application toolkit service (new)", 0x0b => "UIM service", 0x0c => "Phonebook service", 0x0d => "QCHAT Service", 0x0e => "Remote file system service", 0x0f => "Test service", 0x10 => "Location service ", 0x11 => "Specific absorption rate service", 0x12 => "IMS settings service", 0x13 => "Analog to digital converter driver service", 0x14 => "Core sound driver service", 0x15 => "Modem embedded file system service", 0x16 => "Time service", 0x17 => "Thermal sensors service", 0x18 => "Thermal mitigation device service", 0x19 => "Service access proxy service", 0x1a => "Wireless data administrative service", 0x1b => "TSYNC control service ", 0x1c => "Remote file system access service", 0x1d => "Circuit switched videotelephony service", 0x1e => "Qualcomm mobile access point service", 0x1f => "IMS presence service", 0x20 => "IMS videotelephony service", 0x21 => "IMS application service", 0x22 => "Coexistence service", 0x23 => "Reserved", 0x24 => "Persistent device configuration service", 0x25 => "Reserved", 0x26 => "Simultaneous transmit service", 0x27 => "Bearer independent transport service", 0x28 => "IMS RTP service", 0x29 => "RF radiated performance enhancement service", 0x2a => "Data system determination service", 0x2b => "Subsystem control service", 0xe0 => "Card application toolkit service", 0xe1 => "Remote management service", ); # $tlvs = { type1 => packdata, type2 => packdata, .. sub mk_qmi { my ($sys, $cid, $msgid, $tlvs) = @_; # create tlvbytes my $tlvbytes = ''; foreach my $tlv (keys %$tlvs) { $tlvbytes .= pack("Cv", $tlv, length($tlvs->{$tlv})) . $tlvs->{$tlv}; } my $tlvlen = length($tlvbytes); if ($sys != 0) { return pack("CvCCCCvvv", 1, 12 + $tlvlen, 0, $sys, $cid, 0, $tid++, $msgid, $tlvlen) . $tlvbytes; } else { return pack("CvCCCCCvv", 1, 11 + $tlvlen, 0, 0, 0, 0, $tid++, $msgid, $tlvlen) . $tlvbytes; } } sub decode_qmi { my $packet = shift; return {} unless $packet; # printf "%02x " x length($packet) . "\n", unpack("C*", $packet) if $debug; my $ret = {}; @$ret{'tf','len','ctrl','sys','cid'} = unpack("CvCCC", $packet); return {} unless ($ret->{tf} == 1); # tid is 1 byte for QMI_CTL and 2 bytes for the others... @$ret{'flags','tid','msgid','tlvlen'} = unpack($ret->{sys} == 0 ? "CCvv" : "Cvvv" , substr($packet, 6)); my $tlvlen = $ret->{'tlvlen'}; my $tlvs = substr($packet, $ret->{'sys'} == 0 ? 12 : 13 ); # add the tlvs while ($tlvlen > 0) { my ($tlv, $len) = unpack("Cv", $tlvs); $ret->{'tlvs'}{$tlv} = [ unpack("C*", substr($tlvs, 3, $len)) ]; $tlvlen -= $len + 3; $tlvs = substr($tlvs, $len + 3); } return $ret; } sub qmiver { my $qmi = shift; # decode the list of supported systems in TLV 0x01 my @data = @{$qmi->{'tlvs'}{0x01}}; my $n = shift(@data); my $data = pack("C*", @data); print "supports $n QMI subsystems:\n"; for (my $i = 0; $i < $n; $i++) { my ($sys, $maj, $min) = unpack("Cvv", $data); printf " 0x%02x ($maj.$min)\t'%s'\t- %s\n", $sys, $sysname{$sys} || 'unknown', $sysdescr{$sys} || ''; $data = substr($data, 5); } } sub qmiok { my $qmi = shift; return exists($qmi->{tlvs}{0x02}) && (unpack("v", pack("C*", @{$qmi->{tlvs}{0x02}}[2..3])) == 0); } sub do_qmi { my $msgid = shift; my $qmi = shift; my $timeout = shift || 15; printf "QMI>: " . "%02x " x length($qmi) . "\n", unpack("C*", $qmi) if $debug; if ($mbim) { print F &mk_command_msg('EXT_QMUX', 1, 1, $qmi); } else { print F $qmi; } my $count = 10 * $timeout; # seconds timeout my $msg; # wait for a reply, leaving all messages in the queue for (my $i = $timeout; $i > 0; $i--) { ($msg) = grep { !$_->{status} && $_->{qmi}->{msgid} == $msgid } @$msgs; last if $msg; sleep(0.1); } return unless $msg; my $status = &qmiok($msg->{qmi}); printf "QMI msg '0x%04x' returned status = $status\n", $msgid if $verbose; return $status ? $msg->{qmi} : undef; } ## Sierra USB comp my %comps = ( 0 => 'HIP DM NMEA AT MDM1 MDM2 MDM3 MS', 1 => 'HIP DM NMEA AT MDM1 MS', 2 => 'HIP DM NMEA AT NIC1 MS', 3 => 'HIP DM NMEA AT MDM1 NIC1 MS', 4 => 'HIP DM NMEA AT NIC1 NIC2 NIC3 MS', 5 => 'HIP DM NMEA AT ECM1 MS', 6 => 'DM NMEA AT QMI', 7 => 'DM NMEA AT RMNET1 RMNET2 RMNET3', 8 => 'DM NMEA AT MBIM', 9 => 'MBIM', 10 => 'NMEA MBIM', 11 => 'DM MBIM', 12 => 'DM NMEA MBIM', 13 => 'Config1: comp6 Config2: comp8', 14 => 'Config1: comp6 Config2: comp9', 15 => 'Config1: comp6 Config2: comp10', 16 => 'Config1: comp6 Config2: comp11', 17 => 'Config1: comp6 Config2: comp12', 18 => 'Config1: comp7 Config2: comp8', 19 => 'Config1: comp7 Config2: comp9', 20 => 'Config1: comp7 Config2: comp10', 21 => 'Config1: comp7 Config2: comp11', 22 => 'Config1: comp7 Config2: comp12', ); ### main ### # verify that the $mgmt device is a chardev provided by the cdc_mbim driver my ($mode, $rdev) = (stat($mgmt))[2,6]; die "'$mgmt' is not a character device\n" unless S_ISCHR($mode); my $driver = basename(readlink(sprintf("/sys/dev/char/%u:%u/device/driver", &major($rdev), &minor($rdev)))); if ($driver eq "qmi_wwan") { $mbim = undef; } elsif ($driver ne "cdc_mbim") { die "'$mgmt' is provided by '$driver' - only MBIM or QMI devices are supported\n"; } print "Running in ", $mbim ? "MBIM" : "QMI", " mode (driver=$driver)\n"; # open device now and keep it open until exit open(F, "+<", $mgmt) || die "open $mgmt: $!\n"; autoflush F 1; autoflush STDOUT 1; # check message size require 'sys/ioctl.ph'; eval 'sub IOCTL_WDM_MAX_COMMAND () { &_IOC( &_IOC_READ, ord(\'H\'), 0xa0, 2); }' unless defined(&IOCTL_WDM_MAX_COMMAND); my $foo = ''; my $r = ioctl(F, &IOCTL_WDM_MAX_COMMAND, $foo); if ($r) { $maxctrl = unpack("s", $foo); } else { warn("ioctl failed: $!\n") if $debug; } print "MaxMessageSize=$maxctrl\n" if $debug; # fork the reader my $pid = fork(); if ($pid == 0) { # child # shared rx message queue tie $msgs, 'IPC::Shareable', 'msgs', { create => 1, destroy => 0 } || die "tie failed\n"; $msgs = []; &reader(60); # allow up to 60 seconds for the whole transaction print "exiting reader\n" if $debug; exit 0; } elsif (!$pid) { die "fork() failed: $!\n"; } # watch reader status tie $msgs, 'IPC::Shareable', 'msgs', { create => 1, destroy => 1 } || die "tie failed\n"; $msgs = []; if ($mbim) { # send OPEN and wait until reader has seen the OPEN_DONE message print F &mk_open_msg; # flushing all messages until OPEN_DONE while (!grep { $_->{status} == 0x80000001 } @$msgs) { $msgs = []; sleep(1); } print "MBIM OPEN succeeded\n" if $verbose; } my $lastqmi; # verify QMI channel support with QMI_CTL_MESSAGE_GET_VERSION_INFO unless ($lastqmi = &do_qmi(0x0021, &mk_qmi(0, 0, 0x0021, { 0x01 => pack("C", 255), }))) { print "Failed to verify QMI ", $mbim ? "vendor specific MBIM service" : "", "\n"; &quit; } print $mbim ? "MBIM " : "", "QMI support verified\n"; &qmiver($lastqmi) if $verbose; # allocate a DMS CID (or just reuse the one allocated by the MBIM firmware application?) # QMI_CTL_GET_CLIENT_ID, TLV 0x01 => 2 (DMS) unless ($lastqmi = &do_qmi(0x0022, &mk_qmi(0, 0, 0x0022, { 0x01 => pack("C", 2), }))) { print "Failed to get QMI DMS client ID\n"; &quit; } $dmscid = $lastqmi->{'tlvs'}{0x01}[1]; # save the DMS CID print "Got QMI DMS client ID '$dmscid'\n" if $verbose; #QMI_DMS_SWI_SETUSBCOMP (or whatever) # get USB comp = 0x555B # set USB comp = 0x555C # "Set FCC Authentication" = 0x555F ##print F &mk_command_msg('EXT_QMUX', 1, 1, &mk_qmi(2, $dmscid, 0x555c, { 0x01 => $usbcomp})); # wait for response and decode # always get first. We need the list of supported settings to allow set $lastqmi = &do_qmi(0x555b, &mk_qmi(2, $dmscid, 0x555b, {})); &quit unless $lastqmi; my $current = $lastqmi->{'tlvs'}{0x10}[0]; my @supported = @{$lastqmi->{'tlvs'}{0x11}}; my $count = shift(@supported); # basic sanity: if ($count != $#supported + 1) { print "ERROR: array length mismatch, $count != $#supported\n"; print to_json(\@supported),"\n"; &quit; } &quit unless (grep { $current == $_ } @supported); # verify that the current comp is supported # dump current settings printf "Current USB composition: %d\n", $current; if ($verbose) { print "USB compositions:\n"; for my $i (sort { $a <=> $b } keys %comps) { printf "%s %2i - %-48s %sSUPPORTED\n", $i == $current ? '*' : ' ', $i, $comps{$i}, (grep { $i == $_ } @supported) ? '' : 'NOT '; } } # want a new setting? &quit unless defined($usbcomp); # no need to change to the current setting if ($usbcomp == $current) { print "Current setting is already '$usbcomp'\n"; &quit; } # verify that the new setting is supported unless (grep { $usbcomp == $_ } @supported) { print "USB composition '$usbcomp' is not supported\n"; &quit; } # attempt to change USB comp if (!&do_qmi(0x555c, &mk_qmi(2, $dmscid, 0x555c, { 0x01 => pack("C", $usbcomp)}))) { print "Failed to change USB composition to '$usbcomp'\n"; } &quit; sub _slurp { my $f = shift; local $/ = undef; open(X, $f) || return ''; my $ret = ; close(X); $ret =~ tr/\n//d; return $ret; } sub major { my $dev = shift; return ($dev & 0xfff00) >> 8; } sub minor { my $dev = shift; return ($dev & 0xff) | (($dev >> 12) & 0xfff00); } # attempt to reset USB device using devio ioctl sub usbreset { require 'sys/ioctl.ph'; eval 'sub IOCTL_USBDEVFS_RESET () { &_IO(ord(\'U\'), 20); }' unless defined(&IOCTL_USBDEVFS_RESET); # need to find the correct usbdevfs device - this is a bit awkward my $rdev = (stat($mgmt))[6]; my $dev = sprintf("/sys/dev/char/%u:%u/device/..", &major($rdev), &minor($rdev)); my $devnode = sprintf("/dev/bus/usb/%03u/%03u", &_slurp("$dev/busnum"), &_slurp("$dev/devnum")); # this is another one! $rdev = (stat($devnode))[6]; # something wrong unless ($rdev) { print "ERROR: unable to stat '$devnode'\n"; return; } # verify that we got the right one if (&_slurp("$dev/dev") ne sprintf("%u:%u", &major($rdev), &minor($rdev))) { print "ERROR: '$devnode' and '$mgmt' belong to different devices!\n"; return; } my $foo = 0; unless (open(X, ">$devnode")) { print "ERROR: cannot open '$devnode': $!\n"; return; } if (!ioctl(X, &IOCTL_USBDEVFS_RESET, $foo)) { print "USBDEVFS_RESET ioctl failed: $!\n"; } close(X); } sub quit { if ($dmscid) { # reset device? DMS_SET_OPERATING_MODE => RESET if ($reset) { &do_qmi(0x002e, &mk_qmi(2, $dmscid, 0x002e, { 0x01 => pack("C", 4)})); } # release DMS CID # QMI_CTL_RELEASE_CLIENT_ID &do_qmi(0x0023, &mk_qmi(0, 0, 0x0023, { 0x01 => pack("C*", 2, $dmscid)})); } if ($mbim) { # send CLOSE print F &mk_close_msg; } else { # simply signal reader to quit kill 'TERM', $pid; } # wait for the reader to exit (on CLOSE_DONE) waitpid($pid, 0); close(F); # dump all messages received print Dumper($msgs) if $debug; # attempt to reset USB device &usbreset if ($usbreset); exit 0; # will exit parent } sub usage { print STDERR < use for MBIM or QMI commands (default: '$mgmt') --usbcomp= change USB composition setting --reset issue a QMI reset request --usbreset USB device reset - might be necessary for MC74xx --debug enable verbose debug output --help this help text The current setting and supported modes will always be displayed EOH ; exit; }