Project

General

Profile

swi_usbcomp.pl

domi, 08/15/2018 04:37 PM

 
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

    
Add picture from clipboard (Maximum size: 48.8 MB)