1
|
#!/usr/bin/perl
|
2
|
# Copyright (c) 2015 Bjørn Mork <bjorn@mork.no>
|
3
|
# GPLv2
|
4
|
|
5
|
use strict;
|
6
|
use warnings;
|
7
|
use Getopt::Long;
|
8
|
use UUID::Tiny ':std';
|
9
|
use IPC::Shareable;
|
10
|
use Fcntl ':mode';
|
11
|
use File::Basename;
|
12
|
use Time::HiRes qw (sleep);
|
13
|
use Data::Dumper;
|
14
|
|
15
|
my $maxctrl = 4096; # default, will be overridden by ioctl if supported
|
16
|
my $mgmt = "/dev/cdc-wdm0";
|
17
|
my $reset;
|
18
|
my $usbreset;
|
19
|
my $qdl;
|
20
|
my $debug;
|
21
|
my $verbose = 1;
|
22
|
my $usbcomp;
|
23
|
|
24
|
# a few global variables
|
25
|
my $msgs;
|
26
|
my $dmscid;
|
27
|
my $tid = 1;
|
28
|
|
29
|
# defaulting to MBIM mode
|
30
|
my $mbim = 1;
|
31
|
|
32
|
GetOptions(
|
33
|
'usbcomp=i' => \$usbcomp,
|
34
|
'device=s' => \$mgmt,
|
35
|
'reset!' => \$reset,
|
36
|
'usbreset!' => \$usbreset,
|
37
|
'qdl!' => \$qdl,
|
38
|
'debug!' => \$debug,
|
39
|
'verbose!' => \$verbose,
|
40
|
'help|h|?' => \&usage,
|
41
|
) || &usage;
|
42
|
|
43
|
|
44
|
### MBIM helpers ###
|
45
|
sub _push {
|
46
|
my ($buf, $format, @vars) = @_;
|
47
|
|
48
|
my $add = pack($format, @vars);
|
49
|
$buf .= $add;
|
50
|
|
51
|
# update length
|
52
|
my $len = unpack("V", substr($buf, 4, 4));
|
53
|
$len += length($add);
|
54
|
substr($buf, 4, 4) = pack("V", $len);
|
55
|
return $buf;
|
56
|
}
|
57
|
|
58
|
sub _pop {
|
59
|
my ($buf, $format, @vars) = @_;
|
60
|
|
61
|
(@vars) = unpack($format, $buf);
|
62
|
my $x = pack($format, @vars);
|
63
|
return $buf .= pack($format, @vars);
|
64
|
}
|
65
|
|
66
|
my %msg = (
|
67
|
# Table 9‐3: Control messages sent from the host to the function
|
68
|
'MBIM_OPEN_MSG' => 1,
|
69
|
'MBIM_CLOSE_MSG' => 2,
|
70
|
'MBIM_COMMAND_MSG' => 3,
|
71
|
'MBIM_HOST_ERROR_MSG' => 4,
|
72
|
|
73
|
# Table 9‐9: Control Messages sent from function to host
|
74
|
'MBIM_OPEN_DONE' => 0x80000001,
|
75
|
'MBIM_CLOSE_DONE' => 0x80000002,
|
76
|
'MBIM_COMMAND_DONE' => 0x80000003,
|
77
|
'MBIM_FUNCTION_ERROR_MSG' => 0x80000004,
|
78
|
'MBIM_INDICATE_STATUS_MSG' => 0x80000007,
|
79
|
);
|
80
|
|
81
|
# Table 10‐3: Services Defined by MBIM
|
82
|
my %uuid = (
|
83
|
UUID_BASIC_CONNECT => 'a289cc33-bcbb-8b4f-b6b0-133ec2aae6df',
|
84
|
UUID_SMS => '533fbeeb-14fe-4467-9f90-33a223e56c3f',
|
85
|
UUID_USSD => 'e550a0c8-5e82-479e-82f7-10abf4c3351f',
|
86
|
UUID_PHONEBOOK => '4bf38476-1e6a-41db-b1d8-bed289c25bdb',
|
87
|
UUID_STK => 'd8f20131-fcb5-4e17-8602-d6ed3816164c',
|
88
|
UUID_AUTH => '1d2b5ff7-0aa1-48b2-aa52-50f15767174e',
|
89
|
UUID_DSS => 'c08a26dd-7718-4382-8482-6e0d583c4d0e',
|
90
|
|
91
|
# "well known" vendor specific services
|
92
|
UUID_EXT_QMUX => 'd1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3', # ref unknown...
|
93
|
UUID_MULTICARRIER => '8b569648-628d-4653-9b9f-1025404424e1', # ref http://feishare.com/attachments/article/252/implementing-multimode-multicarrier-devices.pdf
|
94
|
UUID_MSFWID => 'e9f7dea2-feaf-4009-93ce-90a3694103b6', # http://msdn.microsoft.com/en-us/library/windows/hardware/jj248721.aspx
|
95
|
UUID_MS_HOSTSHUTDOWN => '883b7c26-985f-43fa-9804-27d7fb80959c', # http://msdn.microsoft.com/en-us/library/windows/hardware/jj248720.aspx
|
96
|
|
97
|
);
|
98
|
|
99
|
sub uuid_to_service {
|
100
|
my $uuid = shift;
|
101
|
my ($service) = grep { $uuid{$_} eq $uuid } keys %uuid;
|
102
|
return 'UNKNOWN' unless $service;
|
103
|
$service =~ s/^UUID_//;
|
104
|
return $service;
|
105
|
}
|
106
|
|
107
|
# MBIM_MESSAGE_HEADER
|
108
|
sub init_msg_header {
|
109
|
my $type = shift;
|
110
|
return &_push('', "VVV", $type, 0, $tid++);
|
111
|
}
|
112
|
|
113
|
# MBIM_FRAGMENT_HEADER
|
114
|
sub push_fragment_header {
|
115
|
my ($buf, $total, $current) = @_;
|
116
|
return $buf = &_push($buf, "VV", $total, $current);
|
117
|
}
|
118
|
|
119
|
# MBIM_OPEN_MSG
|
120
|
sub mk_open_msg {
|
121
|
my $buf = &init_msg_header(1); # MBIM_OPEN_MSG
|
122
|
$buf = &_push($buf, "V", $maxctrl); # MaxControlTransfer
|
123
|
|
124
|
printf "MBIM>: " . "%02x " x length($buf) . "\n", unpack("C*", $buf) if $debug;
|
125
|
return $buf;
|
126
|
}
|
127
|
|
128
|
# MBIM_CLOSE_MSG
|
129
|
sub mk_close_msg {
|
130
|
my $buf = &init_msg_header(2); # MBIM_CLOSE_MSG
|
131
|
|
132
|
printf "MBIM>: " . "%02x " x length($buf) . "\n", unpack("C*", $buf) if $debug;
|
133
|
return $buf;
|
134
|
}
|
135
|
|
136
|
# MBIM_COMMAND_MSG
|
137
|
sub mk_command_msg {
|
138
|
my ($service, $cid, $type, $info) = @_;
|
139
|
|
140
|
my $uuid = string_to_uuid($uuid{"UUID_$service"} || $service) || return '';
|
141
|
my $buf = &init_msg_header(3); # MBIM_COMMAND_MSG
|
142
|
$buf = &push_fragment_header($buf, 1, 0);
|
143
|
$uuid =~ tr/-//d;
|
144
|
$buf = &_push($buf, "a*", $uuid); # DeviceServiceId
|
145
|
$buf = &_push($buf, "VVV",
|
146
|
$cid, # CID
|
147
|
$type, # 0 for a query operation, 1 for a Set operation.
|
148
|
length($info), # InformationBufferLength
|
149
|
);
|
150
|
$buf = &_push($buf, "a*", $info); # InformationBuffer
|
151
|
printf "MBIM>: " . "%02x " x length($buf) . "\n", unpack("C*", $buf) if $debug;
|
152
|
return $buf;
|
153
|
}
|
154
|
|
155
|
sub decode_mbim {
|
156
|
my $msg = shift;
|
157
|
my ($type, $len, $tid) = unpack("VVV", $msg);
|
158
|
|
159
|
if ($debug) {
|
160
|
print "MBIM_MESSAGE_HEADER\n";
|
161
|
printf " MessageType:\t0x%08x\n", $type;
|
162
|
printf " MessageLength:\t%d\n", $len;
|
163
|
printf " TransactionId:\t%d\n", $tid;
|
164
|
}
|
165
|
if ($type == 0x80000001 || $type == 0x80000002) { # MBIM_OPEN_DONE || MBIM_CLOSE_DONE
|
166
|
my $status = unpack("V", substr($msg, 12));
|
167
|
printf " Status:\t0x%08x\n", $status if $debug;
|
168
|
# save message type
|
169
|
push(@$msgs, { status => $type, index => scalar @$msgs, });
|
170
|
} elsif ($type == 0x80000003) { # MBIM_COMMAND_DONE
|
171
|
my ($total, $current) = unpack("VV", substr($msg, 12)); # FragmentHeader
|
172
|
if ($debug) {
|
173
|
print "MBIM_FRAGMENT_HEADER\n";
|
174
|
printf " TotalFragments:\t0x%08x\n", $total;
|
175
|
printf " CurrentFragment:\t0x%08x\n", $current;
|
176
|
}
|
177
|
my $uuid = uuid_to_string(substr($msg, 20, 16));
|
178
|
my $service = &uuid_to_service($uuid);
|
179
|
print "$service ($uuid)\n" if $debug;
|
180
|
|
181
|
my ($cid, $status, $infolen) = unpack("VVV", substr($msg, 36));
|
182
|
my $info = substr($msg, 48);
|
183
|
if ($debug) {
|
184
|
printf " CID:\t\t0x%08x\n", $cid;
|
185
|
printf " Status:\t0x%08x\n", $status;
|
186
|
print "InformationBuffer [$infolen]:\n";
|
187
|
}
|
188
|
if ($infolen != length($info)) {
|
189
|
print "Fragmented MBIM transactions are not supported\n";
|
190
|
} elsif ($service eq "EXT_QMUX") {
|
191
|
# save the decoded QMI message
|
192
|
my $lastqmi = &decode_qmi($info);
|
193
|
# save message
|
194
|
push(@$msgs, { status => 0, index => scalar @$msgs, qmi => $lastqmi}) if $lastqmi;
|
195
|
}
|
196
|
# silently ignoring InformationBuffer payload of other services
|
197
|
}
|
198
|
# ignoring all other types of MBIM messages
|
199
|
}
|
200
|
|
201
|
# read from F until timeout
|
202
|
sub reader {
|
203
|
my $timeout = shift || 0;
|
204
|
|
205
|
eval {
|
206
|
local $SIG{ALRM} = sub { die "timeout\n" };
|
207
|
local $SIG{TERM} = sub { die "close\n" };
|
208
|
my $raw = '';
|
209
|
my $msglen = 0;
|
210
|
alarm $timeout;
|
211
|
do {
|
212
|
my $len = 0;
|
213
|
if ($len < 3 || $len < $msglen) {
|
214
|
my $tmp;
|
215
|
my $n = sysread(F, $tmp, $maxctrl);
|
216
|
if ($n) {
|
217
|
$len = $n;
|
218
|
$raw = $tmp;
|
219
|
printf "%s<: " . "%02x " x $n . "\n", $mbim ? "MBIM" : "QMI", unpack("C*", $tmp) if $debug;
|
220
|
} else {
|
221
|
die "eof\n";
|
222
|
}
|
223
|
}
|
224
|
|
225
|
# get expected message length
|
226
|
if ($mbim) {
|
227
|
$msglen = unpack("V", substr($raw, 4, 4));
|
228
|
} else {
|
229
|
$msglen = unpack("v", substr($raw, 1, 2)) + 1;
|
230
|
}
|
231
|
|
232
|
if ($len >= $msglen) {
|
233
|
$len -= $msglen;
|
234
|
|
235
|
if ($mbim) {
|
236
|
&decode_mbim(substr($raw, 0, $msglen));
|
237
|
die "close\n" if (grep { $_->{status} == 0x80000002 } @$msgs); # exit on CLOSE_DONE
|
238
|
} else {
|
239
|
my $lastqmi = &decode_qmi(substr($raw, 0, $msglen));
|
240
|
push(@$msgs, { status => 0, index => scalar @$msgs, qmi => $lastqmi}) if $lastqmi;
|
241
|
}
|
242
|
$raw = substr($raw, $msglen);
|
243
|
$msglen = 0;
|
244
|
} else {
|
245
|
warn "$len < $msglen\n";
|
246
|
}
|
247
|
} while (1);
|
248
|
alarm 0;
|
249
|
};
|
250
|
if ($@) {
|
251
|
die unless $@ =~ /^close/; # propagate unexpected errors
|
252
|
}
|
253
|
}
|
254
|
|
255
|
### QMI helpers ###
|
256
|
|
257
|
my %sysname = (
|
258
|
0x00 => "QMI_CTL", # Control service
|
259
|
0x01 => "QMI_WDS", # Wireless data service
|
260
|
0x02 => "QMI_DMS", # Device management service
|
261
|
0x03 => "QMI_NAS", # Network access service
|
262
|
0x04 => "QMI_QOS", # Quality of service, err, service
|
263
|
0x05 => "QMI_WMS", # Wireless messaging service
|
264
|
0x06 => "QMI_PDS", # Position determination service
|
265
|
0x07 => "QMI_AUTH", # Authentication service
|
266
|
0x08 => "QMI_AT", # AT command processor service
|
267
|
0x09 => "QMI_VOICE", # Voice service
|
268
|
0x0a => "QMI_CAT2", # Card application toolkit service (new)
|
269
|
0x0b => "QMI_UIM", # UIM service
|
270
|
0x0c => "QMI_PBM", # Phonebook service
|
271
|
0x0d => "QMI_QCHAT", # QCHAT Service
|
272
|
0x0e => "QMI_RMTFS", # Remote file system service
|
273
|
0x0f => "QMI_TEST", # Test service
|
274
|
0x10 => "QMI_LOC", # Location service
|
275
|
0x11 => "QMI_SAR", # Specific absorption rate service
|
276
|
0x12 => "QMI_IMSS", # IMS settings service
|
277
|
0x13 => "QMI_ADC", # Analog to digital converter driver service
|
278
|
0x14 => "QMI_CSD", # Core sound driver service
|
279
|
0x15 => "QMI_MFS", # Modem embedded file system service
|
280
|
0x16 => "QMI_TIME", # Time service
|
281
|
0x17 => "QMI_TS", # Thermal sensors service
|
282
|
0x18 => "QMI_TMD", # Thermal mitigation device service
|
283
|
0x19 => "QMI_SAP", # Service access proxy service
|
284
|
0x1a => "QMI_WDA", # Wireless data administrative service
|
285
|
0x1b => "QMI_TSYNC", # TSYNC control service
|
286
|
0x1c => "QMI_RFSA", # Remote file system access service
|
287
|
0x1d => "QMI_CSVT", # Circuit switched videotelephony service
|
288
|
0x1e => "QMI_QCMAP", # Qualcomm mobile access point service
|
289
|
0x1f => "QMI_IMSP", # IMS presence service
|
290
|
0x20 => "QMI_IMSVT", # IMS videotelephony service
|
291
|
0x21 => "QMI_IMSA", # IMS application service
|
292
|
0x22 => "QMI_COEX", # Coexistence service
|
293
|
0x23 => "QMI_RESERVED_35", # Reserved
|
294
|
0x24 => "QMI_PDC", # Persistent device configuration service
|
295
|
0x25 => "QMI_RESERVED_37", # Reserved
|
296
|
0x26 => "QMI_STX", # Simultaneous transmit service
|
297
|
0x27 => "QMI_BIT", # Bearer independent transport service
|
298
|
0x28 => "QMI_IMSRTP", # IMS RTP service
|
299
|
0x29 => "QMI_RFRPE", # RF radiated performance enhancement service
|
300
|
0x2a => "QMI_DSD", # Data system determination service
|
301
|
0x2b => "QMI_SSCTL", # Subsystem control service
|
302
|
0xe0 => "QMI_CAT", # Card application toolkit service
|
303
|
0xe1 => "QMI_RMS", # Remote management service
|
304
|
);
|
305
|
|
306
|
# dumped from GobiAPI_2013-07-31-1347/GobiConnectionMgmt/GobiConnectionMgmtAPIEnums.h
|
307
|
# using
|
308
|
# 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
|
309
|
my %sysdescr = (
|
310
|
0x00 => "Control service",
|
311
|
0x01 => "Wireless data service",
|
312
|
0x02 => "Device management service",
|
313
|
0x03 => "Network access service",
|
314
|
0x04 => "Quality of service, err, service ",
|
315
|
0x05 => "Wireless messaging service",
|
316
|
0x06 => "Position determination service",
|
317
|
0x07 => "Authentication service",
|
318
|
0x08 => "AT command processor service",
|
319
|
0x09 => "Voice service",
|
320
|
0x0a => "Card application toolkit service (new)",
|
321
|
0x0b => "UIM service",
|
322
|
0x0c => "Phonebook service",
|
323
|
0x0d => "QCHAT Service",
|
324
|
0x0e => "Remote file system service",
|
325
|
0x0f => "Test service",
|
326
|
0x10 => "Location service ",
|
327
|
0x11 => "Specific absorption rate service",
|
328
|
0x12 => "IMS settings service",
|
329
|
0x13 => "Analog to digital converter driver service",
|
330
|
0x14 => "Core sound driver service",
|
331
|
0x15 => "Modem embedded file system service",
|
332
|
0x16 => "Time service",
|
333
|
0x17 => "Thermal sensors service",
|
334
|
0x18 => "Thermal mitigation device service",
|
335
|
0x19 => "Service access proxy service",
|
336
|
0x1a => "Wireless data administrative service",
|
337
|
0x1b => "TSYNC control service ",
|
338
|
0x1c => "Remote file system access service",
|
339
|
0x1d => "Circuit switched videotelephony service",
|
340
|
0x1e => "Qualcomm mobile access point service",
|
341
|
0x1f => "IMS presence service",
|
342
|
0x20 => "IMS videotelephony service",
|
343
|
0x21 => "IMS application service",
|
344
|
0x22 => "Coexistence service",
|
345
|
0x23 => "Reserved",
|
346
|
0x24 => "Persistent device configuration service",
|
347
|
0x25 => "Reserved",
|
348
|
0x26 => "Simultaneous transmit service",
|
349
|
0x27 => "Bearer independent transport service",
|
350
|
0x28 => "IMS RTP service",
|
351
|
0x29 => "RF radiated performance enhancement service",
|
352
|
0x2a => "Data system determination service",
|
353
|
0x2b => "Subsystem control service",
|
354
|
0xe0 => "Card application toolkit service",
|
355
|
0xe1 => "Remote management service",
|
356
|
);
|
357
|
|
358
|
# $tlvs = { type1 => packdata, type2 => packdata, ..
|
359
|
sub mk_qmi {
|
360
|
my ($sys, $cid, $msgid, $tlvs) = @_;
|
361
|
|
362
|
# create tlvbytes
|
363
|
my $tlvbytes = '';
|
364
|
foreach my $tlv (keys %$tlvs) {
|
365
|
$tlvbytes .= pack("Cv", $tlv, length($tlvs->{$tlv})) . $tlvs->{$tlv};
|
366
|
}
|
367
|
my $tlvlen = length($tlvbytes);
|
368
|
if ($sys != 0) {
|
369
|
return pack("CvCCCCvvv", 1, 12 + $tlvlen, 0, $sys, $cid, 0, $tid++, $msgid, $tlvlen) . $tlvbytes;
|
370
|
} else {
|
371
|
return pack("CvCCCCCvv", 1, 11 + $tlvlen, 0, 0, 0, 0, $tid++, $msgid, $tlvlen) . $tlvbytes;
|
372
|
}
|
373
|
}
|
374
|
|
375
|
sub decode_qmi {
|
376
|
my $packet = shift;
|
377
|
return {} unless $packet;
|
378
|
|
379
|
# printf "%02x " x length($packet) . "\n", unpack("C*", $packet) if $debug;
|
380
|
|
381
|
my $ret = {};
|
382
|
@$ret{'tf','len','ctrl','sys','cid'} = unpack("CvCCC", $packet);
|
383
|
return {} unless ($ret->{tf} == 1);
|
384
|
|
385
|
# tid is 1 byte for QMI_CTL and 2 bytes for the others...
|
386
|
@$ret{'flags','tid','msgid','tlvlen'} = unpack($ret->{sys} == 0 ? "CCvv" : "Cvvv" , substr($packet, 6));
|
387
|
my $tlvlen = $ret->{'tlvlen'};
|
388
|
my $tlvs = substr($packet, $ret->{'sys'} == 0 ? 12 : 13 );
|
389
|
|
390
|
# add the tlvs
|
391
|
while ($tlvlen > 0) {
|
392
|
my ($tlv, $len) = unpack("Cv", $tlvs);
|
393
|
$ret->{'tlvs'}{$tlv} = [ unpack("C*", substr($tlvs, 3, $len)) ];
|
394
|
$tlvlen -= $len + 3;
|
395
|
$tlvs = substr($tlvs, $len + 3);
|
396
|
}
|
397
|
return $ret;
|
398
|
}
|
399
|
|
400
|
sub qmiver {
|
401
|
my $qmi = shift;
|
402
|
|
403
|
# decode the list of supported systems in TLV 0x01
|
404
|
my @data = @{$qmi->{'tlvs'}{0x01}};
|
405
|
my $n = shift(@data);
|
406
|
my $data = pack("C*", @data);
|
407
|
print "supports $n QMI subsystems:\n";
|
408
|
for (my $i = 0; $i < $n; $i++) {
|
409
|
my ($sys, $maj, $min) = unpack("Cvv", $data);
|
410
|
printf " 0x%02x ($maj.$min)\t'%s'\t- %s\n", $sys, $sysname{$sys} || 'unknown', $sysdescr{$sys} || '';
|
411
|
$data = substr($data, 5);
|
412
|
}
|
413
|
}
|
414
|
|
415
|
sub qmiok {
|
416
|
my $qmi = shift;
|
417
|
return exists($qmi->{tlvs}{0x02}) && (unpack("v", pack("C*", @{$qmi->{tlvs}{0x02}}[2..3])) == 0);
|
418
|
}
|
419
|
|
420
|
sub do_qmi {
|
421
|
my $msgid = shift;
|
422
|
my $qmi = shift;
|
423
|
my $timeout = shift || 15;
|
424
|
|
425
|
printf "QMI>: " . "%02x " x length($qmi) . "\n", unpack("C*", $qmi) if $debug;
|
426
|
|
427
|
if ($mbim) {
|
428
|
print F &mk_command_msg('EXT_QMUX', 1, 1, $qmi);
|
429
|
} else {
|
430
|
print F $qmi;
|
431
|
}
|
432
|
my $count = 10 * $timeout; # seconds timeout
|
433
|
my $msg;
|
434
|
|
435
|
# wait for a reply, leaving all messages in the queue
|
436
|
for (my $i = $timeout; $i > 0; $i--) {
|
437
|
($msg) = grep { !$_->{status} && $_->{qmi}->{msgid} == $msgid } @$msgs;
|
438
|
last if $msg;
|
439
|
sleep(0.1);
|
440
|
}
|
441
|
return unless $msg;
|
442
|
|
443
|
my $status = &qmiok($msg->{qmi});
|
444
|
printf "QMI msg '0x%04x' returned status = $status\n", $msgid if $verbose;
|
445
|
return $status ? $msg->{qmi} : undef;
|
446
|
}
|
447
|
|
448
|
|
449
|
## Sierra USB comp
|
450
|
my %comps = (
|
451
|
0 => 'HIP DM NMEA AT MDM1 MDM2 MDM3 MS',
|
452
|
1 => 'HIP DM NMEA AT MDM1 MS',
|
453
|
2 => 'HIP DM NMEA AT NIC1 MS',
|
454
|
3 => 'HIP DM NMEA AT MDM1 NIC1 MS',
|
455
|
4 => 'HIP DM NMEA AT NIC1 NIC2 NIC3 MS',
|
456
|
5 => 'HIP DM NMEA AT ECM1 MS',
|
457
|
6 => 'DM NMEA AT QMI',
|
458
|
7 => 'DM NMEA AT RMNET1 RMNET2 RMNET3',
|
459
|
8 => 'DM NMEA AT MBIM',
|
460
|
9 => 'MBIM',
|
461
|
10 => 'NMEA MBIM',
|
462
|
11 => 'DM MBIM',
|
463
|
12 => 'DM NMEA MBIM',
|
464
|
13 => 'Config1: comp6 Config2: comp8',
|
465
|
14 => 'Config1: comp6 Config2: comp9',
|
466
|
15 => 'Config1: comp6 Config2: comp10',
|
467
|
16 => 'Config1: comp6 Config2: comp11',
|
468
|
17 => 'Config1: comp6 Config2: comp12',
|
469
|
18 => 'Config1: comp7 Config2: comp8',
|
470
|
19 => 'Config1: comp7 Config2: comp9',
|
471
|
20 => 'Config1: comp7 Config2: comp10',
|
472
|
21 => 'Config1: comp7 Config2: comp11',
|
473
|
22 => 'Config1: comp7 Config2: comp12',
|
474
|
);
|
475
|
|
476
|
### main ###
|
477
|
|
478
|
|
479
|
# verify that the $mgmt device is a chardev provided by the cdc_mbim driver
|
480
|
my ($mode, $rdev) = (stat($mgmt))[2,6];
|
481
|
die "'$mgmt' is not a character device\n" unless S_ISCHR($mode);
|
482
|
my $driver = basename(readlink(sprintf("/sys/dev/char/%u:%u/device/driver", &major($rdev), &minor($rdev))));
|
483
|
if ($driver eq "qmi_wwan") {
|
484
|
$mbim = undef;
|
485
|
} elsif ($driver ne "cdc_mbim") {
|
486
|
die "'$mgmt' is provided by '$driver' - only MBIM or QMI devices are supported\n";
|
487
|
}
|
488
|
|
489
|
print "Running in ", $mbim ? "MBIM" : "QMI", " mode (driver=$driver)\n";
|
490
|
|
491
|
# open device now and keep it open until exit
|
492
|
open(F, "+<", $mgmt) || die "open $mgmt: $!\n";
|
493
|
autoflush F 1;
|
494
|
autoflush STDOUT 1;
|
495
|
|
496
|
# check message size
|
497
|
require 'sys/ioctl.ph';
|
498
|
eval 'sub IOCTL_WDM_MAX_COMMAND () { &_IOC( &_IOC_READ, ord(\'H\'), 0xa0, 2); }' unless defined(&IOCTL_WDM_MAX_COMMAND);
|
499
|
my $foo = '';
|
500
|
my $r = ioctl(F, &IOCTL_WDM_MAX_COMMAND, $foo);
|
501
|
if ($r) {
|
502
|
$maxctrl = unpack("s", $foo);
|
503
|
} else {
|
504
|
warn("ioctl failed: $!\n") if $debug;
|
505
|
}
|
506
|
print "MaxMessageSize=$maxctrl\n" if $debug;
|
507
|
|
508
|
# fork the reader
|
509
|
my $pid = fork();
|
510
|
if ($pid == 0) { # child
|
511
|
# shared rx message queue
|
512
|
tie $msgs, 'IPC::Shareable', 'msgs', { create => 1, destroy => 0 } || die "tie failed\n";
|
513
|
$msgs = [];
|
514
|
&reader(60); # allow up to 60 seconds for the whole transaction
|
515
|
print "exiting reader\n" if $debug;
|
516
|
exit 0;
|
517
|
} elsif (!$pid) {
|
518
|
die "fork() failed: $!\n";
|
519
|
}
|
520
|
|
521
|
# watch reader status
|
522
|
tie $msgs, 'IPC::Shareable', 'msgs', { create => 1, destroy => 1 } || die "tie failed\n";
|
523
|
$msgs = [];
|
524
|
|
525
|
if ($mbim) {
|
526
|
# send OPEN and wait until reader has seen the OPEN_DONE message
|
527
|
print F &mk_open_msg;
|
528
|
|
529
|
# flushing all messages until OPEN_DONE
|
530
|
while (!grep { $_->{status} == 0x80000001 } @$msgs) {
|
531
|
$msgs = [];
|
532
|
sleep(1);
|
533
|
}
|
534
|
print "MBIM OPEN succeeded\n" if $verbose;
|
535
|
}
|
536
|
|
537
|
my $lastqmi;
|
538
|
|
539
|
# verify QMI channel support with QMI_CTL_MESSAGE_GET_VERSION_INFO
|
540
|
unless ($lastqmi = &do_qmi(0x0021, &mk_qmi(0, 0, 0x0021, { 0x01 => pack("C", 255), }))) {
|
541
|
print "Failed to verify QMI ", $mbim ? "vendor specific MBIM service" : "", "\n";
|
542
|
&quit;
|
543
|
}
|
544
|
print $mbim ? "MBIM " : "", "QMI support verified\n";
|
545
|
|
546
|
&qmiver($lastqmi) if $verbose;
|
547
|
|
548
|
# allocate a DMS CID (or just reuse the one allocated by the MBIM firmware application?)
|
549
|
# QMI_CTL_GET_CLIENT_ID, TLV 0x01 => 2 (DMS)
|
550
|
unless ($lastqmi = &do_qmi(0x0022, &mk_qmi(0, 0, 0x0022, { 0x01 => pack("C", 2), }))) {
|
551
|
print "Failed to get QMI DMS client ID\n";
|
552
|
&quit;
|
553
|
}
|
554
|
$dmscid = $lastqmi->{'tlvs'}{0x01}[1]; # save the DMS CID
|
555
|
print "Got QMI DMS client ID '$dmscid'\n" if $verbose;
|
556
|
|
557
|
|
558
|
# Bootloader mode trumps the rest of this script....
|
559
|
if ($qdl) {
|
560
|
$lastqmi = &do_qmi(0x003e, &mk_qmi(2, $dmscid, 0x003e, {}));
|
561
|
&quit;
|
562
|
}
|
563
|
|
564
|
#QMI_DMS_SWI_SETUSBCOMP (or whatever)
|
565
|
# get USB comp = 0x555B
|
566
|
# set USB comp = 0x555C
|
567
|
# "Set FCC Authentication" = 0x555F
|
568
|
##print F &mk_command_msg('EXT_QMUX', 1, 1, &mk_qmi(2, $dmscid, 0x555c, { 0x01 => $usbcomp}));
|
569
|
# wait for response and decode
|
570
|
|
571
|
# always get first. We need the list of supported settings to allow set
|
572
|
$lastqmi = &do_qmi(0x555b, &mk_qmi(2, $dmscid, 0x555b, {}));
|
573
|
&quit unless $lastqmi;
|
574
|
my $current = $lastqmi->{'tlvs'}{0x10}[0];
|
575
|
my @supported = @{$lastqmi->{'tlvs'}{0x11}};
|
576
|
my $count = shift(@supported);
|
577
|
|
578
|
# basic sanity:
|
579
|
if ($count != $#supported + 1) {
|
580
|
print "ERROR: array length mismatch, $count != $#supported\n";
|
581
|
print to_json(\@supported),"\n";
|
582
|
&quit;
|
583
|
}
|
584
|
|
585
|
&quit unless (grep { $current == $_ } @supported); # verify that the current comp is supported
|
586
|
|
587
|
# dump current settings
|
588
|
printf "Current USB composition: %d\n", $current;
|
589
|
if ($verbose) {
|
590
|
print "USB compositions:\n";
|
591
|
for my $i (sort { $a <=> $b } keys %comps) {
|
592
|
printf "%s %2i - %-48s %sSUPPORTED\n", $i == $current ? '*' : ' ', $i, $comps{$i}, (grep { $i == $_ } @supported) ? '' : 'NOT ';
|
593
|
}
|
594
|
}
|
595
|
|
596
|
# want a new setting?
|
597
|
&quit unless defined($usbcomp);
|
598
|
|
599
|
# no need to change to the current setting
|
600
|
if ($usbcomp == $current) {
|
601
|
print "Current setting is already '$usbcomp'\n";
|
602
|
&quit;
|
603
|
}
|
604
|
|
605
|
# verify that the new setting is supported
|
606
|
unless (grep { $usbcomp == $_ } @supported) {
|
607
|
print "USB composition '$usbcomp' is not supported\n";
|
608
|
&quit;
|
609
|
}
|
610
|
|
611
|
# attempt to change USB comp
|
612
|
if (!&do_qmi(0x555c, &mk_qmi(2, $dmscid, 0x555c, { 0x01 => pack("C", $usbcomp)}))) {
|
613
|
print "Failed to change USB composition to '$usbcomp'\n";
|
614
|
}
|
615
|
|
616
|
&quit;
|
617
|
|
618
|
sub _slurp {
|
619
|
my $f = shift;
|
620
|
local $/ = undef;
|
621
|
open(X, $f) || return '';
|
622
|
my $ret = <X>;
|
623
|
close(X);
|
624
|
$ret =~ tr/\n//d;
|
625
|
return $ret;
|
626
|
}
|
627
|
|
628
|
sub major
|
629
|
{
|
630
|
my $dev = shift;
|
631
|
return ($dev & 0xfff00) >> 8;
|
632
|
}
|
633
|
|
634
|
sub minor
|
635
|
{
|
636
|
my $dev = shift;
|
637
|
return ($dev & 0xff) | (($dev >> 12) & 0xfff00);
|
638
|
}
|
639
|
|
640
|
# attempt to reset USB device using devio ioctl
|
641
|
sub usbreset {
|
642
|
require 'sys/ioctl.ph';
|
643
|
eval 'sub IOCTL_USBDEVFS_RESET () { &_IO(ord(\'U\'), 20); }' unless defined(&IOCTL_USBDEVFS_RESET);
|
644
|
|
645
|
# need to find the correct usbdevfs device - this is a bit awkward
|
646
|
my $rdev = (stat($mgmt))[6];
|
647
|
my $dev = sprintf("/sys/dev/char/%u:%u/device/..", &major($rdev), &minor($rdev));
|
648
|
my $devnode = sprintf("/dev/bus/usb/%03u/%03u", &_slurp("$dev/busnum"), &_slurp("$dev/devnum"));
|
649
|
|
650
|
# this is another one!
|
651
|
$rdev = (stat($devnode))[6];
|
652
|
|
653
|
# something wrong
|
654
|
unless ($rdev) {
|
655
|
print "ERROR: unable to stat '$devnode'\n";
|
656
|
return;
|
657
|
}
|
658
|
|
659
|
# verify that we got the right one
|
660
|
if (&_slurp("$dev/dev") ne sprintf("%u:%u", &major($rdev), &minor($rdev))) {
|
661
|
print "ERROR: '$devnode' and '$mgmt' belong to different devices!\n";
|
662
|
return;
|
663
|
}
|
664
|
|
665
|
my $foo = 0;
|
666
|
unless (open(X, ">$devnode")) {
|
667
|
print "ERROR: cannot open '$devnode': $!\n";
|
668
|
return;
|
669
|
}
|
670
|
if (!ioctl(X, &IOCTL_USBDEVFS_RESET, $foo)) {
|
671
|
print "USBDEVFS_RESET ioctl failed: $!\n";
|
672
|
}
|
673
|
close(X);
|
674
|
}
|
675
|
|
676
|
sub quit {
|
677
|
if ($dmscid) {
|
678
|
# reset device? DMS_SET_OPERATING_MODE => RESET
|
679
|
if ($reset) {
|
680
|
&do_qmi(0x002e, &mk_qmi(2, $dmscid, 0x002e, { 0x01 => pack("C", 4)}));
|
681
|
}
|
682
|
|
683
|
# release DMS CID
|
684
|
# QMI_CTL_RELEASE_CLIENT_ID
|
685
|
&do_qmi(0x0023, &mk_qmi(0, 0, 0x0023, { 0x01 => pack("C*", 2, $dmscid)}));
|
686
|
}
|
687
|
|
688
|
if ($mbim) {
|
689
|
# send CLOSE
|
690
|
print F &mk_close_msg;
|
691
|
} else {
|
692
|
# simply signal reader to quit
|
693
|
kill 'TERM', $pid;
|
694
|
}
|
695
|
|
696
|
# wait for the reader to exit (on CLOSE_DONE)
|
697
|
waitpid($pid, 0);
|
698
|
|
699
|
close(F);
|
700
|
|
701
|
# dump all messages received
|
702
|
## print Dumper($msgs) if $debug;
|
703
|
|
704
|
# attempt to reset USB device
|
705
|
&usbreset if ($usbreset);
|
706
|
|
707
|
exit 0; # will exit parent
|
708
|
}
|
709
|
|
710
|
sub usage {
|
711
|
print STDERR <<EOH
|
712
|
Usage: $0 [options]
|
713
|
|
714
|
Where [options] are
|
715
|
--device=<dev> use <dev> for MBIM or QMI commands (default: '$mgmt')
|
716
|
--usbcomp=<num> change USB composition setting
|
717
|
--reset issue a QMI reset request
|
718
|
--usbreset USB device reset - might be necessary for MC74xx
|
719
|
--qdl reboot modem into bootloader QDL mode
|
720
|
--debug enable verbose debug output
|
721
|
--help this help text
|
722
|
|
723
|
The current setting and supported modes will always be displayed
|
724
|
|
725
|
|
726
|
EOH
|
727
|
;
|
728
|
exit;
|
729
|
}
|
730
|
|