summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2015-06-30 16:03:19 +0200
committerBjørn Mork <bjorn@mork.no>2015-06-30 16:03:19 +0200
commit5b50c5e49793eec3c1cb17079fe9d4fb212140b0 (patch)
tree832495ab75bdc5b5e5a7a0f598293ee11cc96eb3
parent9b7febff6d07d48ad675c5af288158f7a2795269 (diff)
swi_setusbcoattempt to use QMI DMS SWI Set USB Comp via MBIM
Signed-off-by: Bjørn Mork <bjorn@mork.no>
-rw-r--r--scripts/swi_setusbcomp.pl320
1 files changed, 320 insertions, 0 deletions
diff --git a/scripts/swi_setusbcomp.pl b/scripts/swi_setusbcomp.pl
new file mode 100644
index 0000000..8903b6e
--- /dev/null
+++ b/scripts/swi_setusbcomp.pl
@@ -0,0 +1,320 @@
+#!/usr/bin/perl
+# Copyright (c) 2015 Bjørn Mork <bjorn@mork.no>
+# GPLv2
+
+use strict;
+use warnings;
+use Data::Dumper;
+use Getopt::Long;
+use Encode;
+use Devel::SimpleTrace;
+use UUID::Tiny ':std';
+
+my $maxctrl = 4096; # default, will be overridden by ioctl if supported
+my $mgmt = "/dev/cdc-wdm0";
+my $debug;
+
+GetOptions(
+ 'device=s' => \$mgmt,
+ 'debug!' => \$debug,
+ '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
+
+ if ($debug) {
+ my $n = length($buf);
+ warn("[" . localtime . "] sending $n bytes to $mgmt\n");
+ print "\n---\n";
+ printf "%02x " x $n, unpack("C*", $buf);
+ print "\n---\n";
+ }
+
+ return $buf;
+}
+
+# MBIM_CLOSE_MSG
+sub mk_close_msg {
+ my $buf = &init_msg_header(2); # MBIM_CLOSE_MSG
+
+ if ($debug) {
+ my $n = length($buf);
+ warn("[" . localtime . "] sending $n bytes to $mgmt\n");
+ print "\n---\n";
+ printf "%02x " x $n, unpack("C*", $buf);
+ print "\n---\n";
+ }
+
+ 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
+
+ if ($debug) {
+ my $n = length($buf);
+ warn("[" . localtime . "] sending $n bytes to $mgmt\n");
+ print "\n---\n";
+ printf "%02x " x $n, unpack("C*", $buf);
+ print "\n---\n";
+ }
+ return $buf;
+}
+
+sub decode_mbim {
+ my $msg = shift;
+ my ($type, $len, $tid) = unpack("VVV", $msg);
+
+ 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;
+ } elsif ($type == 0x80000003) { # MBIM_COMMAND_DONE
+ my ($total, $current) = unpack("VV", substr($msg, 12)); # FragmentHeader
+ 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";
+
+ my ($cid, $status, $infolen) = unpack("VVV", substr($msg, 36));
+ my $info = substr($msg, 48);
+ printf " CID:\t0x%08x\n", $cid;
+ printf " Status:\t0x%08x\n", $status;
+ print "InformationBuffer [$infolen]:\n";
+ if ($infolen != length($info)) {
+ print "Fragmented data is not supported\n";
+ } elsif ($service eq "EXT_QMUX") {
+ print Dumper(&decode_qmi($info));
+ }
+ # silently ignoring InformationBuffer payload of other services
+ }
+ # ignoring all other types of MBIM messages
+}
+
+### QMI helpers ###
+
+use constant {
+ QMI_CTL => 0x00,
+ QMI_WDS => 0x01,
+ QMI_DMS => 0x02,
+ QMI_NAS => 0x03,
+ QMI_WMS => 0x05,
+ QMI_PDS => 0x06,
+ QMI_LOC => 0x10,
+};
+
+# $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 != QMI_CTL) {
+ return pack("CvCCCCvvv", 1, 12 + $tlvlen, 0, $sys, $cid, 0, $tid++, $msgid, $tlvlen) . $tlvbytes;
+ } else {
+ return pack("CvCCCCCvv", 1, 11 + $tlvlen, 0, QMI_CTL, 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} == QMI_CTL ? "CCvv" : "Cvvv" , substr($packet, 6));
+ my $tlvlen = $ret->{'tlvlen'};
+ my $tlvs = substr($packet, $ret->{'sys'} == QMI_CTL ? 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;
+}
+
+### main ###
+
+# open device now and keep it open until exit
+open(F, "+<", $mgmt) || die "open $mgmt: $!\n";
+autoflush F 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;
+
+print F &mk_open_msg;
+# wait for OPEN
+
+
+# test QMI
+
+# FIXME: simpler to import a QMI encoder, than to manually fix up all these static arrays. And so much more readable...
+
+# QMI_CTL_MESSAGE_GET_VERSION_INFO
+print F &mk_command_msg('EXT_QMUX', 1, 1, &mk_qmi(0, 0, 0x0021, { 0x01 => 0xff }));
+
+# wait for response (and decode?)
+
+# allocate a DMS CID (or just reuse the one allocated by the MBIM firmware application?)
+# QMI_CTL_GET_CLIENT_ID, TLV 0x01 => 2 (DMS)
+print F &mk_command_msg('EXT_QMUX', 1, 1, &mk_qmi(0, 0, 0x0022, { 0x01 => 2 }));
+
+# wait for response and decode
+# => $dmscid
+
+
+#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
+
+#let's test a get first, eh?
+print F &mk_command_msg('EXT_QMUX', 1, 1, &mk_qmi(2, $dmscid, 0x555b));
+# wait for response and decode
+
+# release DMS CID
+# QMI_CTL_RELEASE_CLIENT_ID
+print F &mk_command_msg('EXT_QMUX', 1, 1, &mk_qmi(0, 0, 0x0022, { 0x01 => pack("C*", 2, $dmscid)}));
+# wait for response and decode
+
+print F &mk_close_msg;
+# wait for response, close and exit
+
+# close device
+close(F);
+
+
+
+sub usage {
+ print STDERR <<EOH
+Usage: $0 [options]
+
+Where [options] are
+
+
+EOH
+ ;
+ exit;
+}