Re: [PATCH] USB: option: Add USB ID for Novatel Ovation MC551

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Dan Williams <dcbw@xxxxxxxxxx> writes:

> This was just a comment to let others (Bjorn!) know that we'll *also*
> probably need some more ids in cdc_wdm.  I'm not 100% certain the device
> does QMI, but I'm 95% sure.

Thanks for the confidence, but I don't see how I can guess this at
all. The only way to know for sure is if one of you with the device can
test it.

I am attaching my perl example of how QMI support can be tested without
any driver support or other QMI utilities. It creates a CDC embedded
QMI_CTL "Get Version" request and sends that using libusb to all possible
interfaces on a device.  No magic.  If we get a response, then the
interface supports QMI.  Otherwise it doesn't.

The requirements are simply the Device::USB libusb perl wrapper and
access rights to the device. No specific drivers or kernel versions
(except for the libusb support).  Note that the script will unbind any
kernel driver from the interfaces it wants to test, and won't rebind
them when finished.  Simply replugging the device after testing should
reset everything.


A short demo:

1) These are my USB devices:

 bjorn@nemi:/tmp$ lsusb
 Bus 001 Device 003: ID 17ef:4807 Lenovo UVC Camera
 Bus 002 Device 002: ID 08ff:2810 AuthenTec, Inc. AES2810
 Bus 006 Device 043: ID 1199:68a2 Sierra Wireless, Inc. 
 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
 Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
 Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
 Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
 Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
 Bus 006 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
 Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
 Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

2) Need full access to the device under test, but don't want to let the
   script run as root.  Taking the bus and device number from lsusb
   output:

 nemi:/tmp# chown bjorn /dev/bus/usb/006/043 


3) Run the script and let it test every possible interface on this
   device:

 bjorn@nemi:/tmp$ ./qmiver.pl --device=1199:68a2
 Debugging: off
 Device: 1199:68a2
 Unsupported endpoint configuration on ifnum=0
 Unsupported endpoint configuration on ifnum=2
 Candidate: ifnum=3
 unbinding interface 3 from kernel driver "qcserial"
 control_msg() failed (-32): Broken pipe
 Candidate: ifnum=8
 unbinding interface 8 from kernel driver "qmi_wwan"
 supports 17 QMI subsystems:
   QMI_CTL (1.5)
   QMI_WDS (1.12)
   QMI_DMS (1.7)
   QMI_NAS (1.16)
   QMI_QOS (1.3)
   QMI_WMS (1.4)
   QMI_PDS (1.10)
   QMI_AUTH (1.1)
   QMI_AT (1.1)
   QMI_VOICE (2.1)
   QMI_CAT (2.0)
   QMI UIM (1.4)
   QMI PBM (1.4)
   QMI_SAR (1.0)
   0x1a (1.0)
   QMI_CAT (2.0)
   QMI_RMS (1.0)



That's all.  Unplug and replug the device to have both access rights
reset and drivers rebound.

I believe that is about as simple test as you can get, though I am sure
you can rewrite it as a much nicer python script :-)



Bjørn

#!/usr/bin/perl
# Copyright 2012 Bjørn Mork <bjorn@xxxxxxx>
# License: GPLv2

use strict;
use warnings;
use Getopt::Long;
use Device::USB;
use Data::Dumper;

# defaults
my %opt = (
    'debug' => 0, 
);

GetOptions(\%opt,
	   'device=s',
	   'if=s',
	   'debug|d+',
	   'help|h|?',
    );

&usage if ($opt{'help'} || !$opt{'device'});

my %sysname = (
    0    => "QMI_CTL",
    1    => "QMI_WDS",
    2    => "QMI_DMS",
    3    => "QMI_NAS",
    4    => "QMI_QOS",
    5    => "QMI_WMS",
    6    => "QMI_PDS",
    7    => "QMI_AUTH",
    8    => "QMI_AT",
    9    => "QMI_VOICE",
    0xa  => "QMI_CAT",
    0xb  => "QMI UIM",
    0xc  => "QMI PBM",
    0x10 => "QMI_LOC",
    0x11 => "QMI_SAR",
    0xe0 => "QMI_CAT", # duplicate!
    0xe1 => "QMI_RMS",
    0xe2 => "QMI_OMA",
    );

my $usb = Device::USB->new();
$usb->debug_mode($opt{'debug'});

my ($vid, $pid) = map { hex } split(/:/, $opt{'device'});

my $dev = $usb->find_device($vid, $pid);
die "Cannot find any such device: $opt{'device'} - $!\n" unless $dev;

warn "Device: ", sprintf("%04x:%04x", $dev->idVendor(), $dev->idProduct()), "\n";

$dev->open();

my $cfg = $dev->config()->[0];

# cannot use ifnum as array idx as the numbering may not be consecutive...
my @intflist = grep { !$opt{'if'} || $_->[0]->bInterfaceNumber == $opt{'if'} } @{$cfg->interfaces()};
warn Dumper(\@intflist) if $opt{'debug'};

foreach (@intflist) {
    my $intf = $_->[0];
  
    # supported interfaces must have an interrupt endpoint and may have 2 bulk endpoints
    if (($intf->bNumEndpoints == 3 || $intf->bNumEndpoints == 1) &&
	($intf->endpoints->[0]->bmAttributes == 3)) {  # USB_ENDPOINT_XFER_INT
	warn "Candidate: ifnum=", $intf->bInterfaceNumber,"\n";
	&do_qmi($dev, $intf);
    } else {
	warn "Unsupported endpoint configuration on ifnum=", $intf->bInterfaceNumber,"\n";
    }
}


sub do_qmi {
    my ($dev, $intf) = @_;

    my $ifnum = $intf->bInterfaceNumber;
    my $driver = $dev->get_driver_np($ifnum);
    if ($driver) {
	warn "unbinding interface $ifnum from kernel driver \"$driver\"\n";
	if ($dev->detach_kernel_driver_np($ifnum) < 0) {
	    warn "unbinding FAILED\n";
	    return undef;
	}
    }
    my $ret = $dev->claim_interface($ifnum);
    if ($ret < 0) {
	warn "claim_interface failed ($ret): $!\n";
	return;
    }

    # QMI_CTL_MESSAGE_GET_VERSION_INFO
    my $qmi = pack("C*", map { hex } qw!01 0b 00 00 00 00 00 08 21 00 00 00!);
    &send_msg($dev, $ifnum, $qmi, length($qmi));

    # may have to skip a few unsolicted messages
    for (my $i = 0; $i < 10; $i++) {
	my ($len, $msg) = &recv_msg($dev, $ifnum);
	last if ($len < 0); # no need to repeat if complete failure
	last if ($len && &is_ver($msg, $len));
	# just give the device "enough time"...
	sleep(.5);
    }
    $dev->release_interface($ifnum);
}

#map { warn "num: ", $_->bInterfaceNumber(), "eps: ", $_->nNumEndpoints(), "\n" } @{$cfg->interfaces()};

# test if msg is a reply to a QMI_CTL_MESSAGE_GET_VERSION_INFO and print details if it is
sub is_ver {
    my ($msg, $len) = @_;

    warn sprintf "%02x " x length($msg) . "\n", unpack("C*", $msg) if $opt{'debug'};

    my $ret = {};
    @$ret{'tf','len','ctrl','sys','cid','flags','tid','msgid','tlvlen'} = unpack("CvCCCCCvv", $msg);

    # sanity check: tf is always one, sys must be QMI_CTL
    return undef unless ($ret->{tf} == 1 && $ret->{sys} == 0);

    # only interested in QMI_CTL_MESSAGE_GET_VERSION_INFO
    return undef unless ($ret->{'msgid'} == 0x0021);

    # add the tlv(s)
    my $tlvlen = $ret->{'tlvlen'};
    my $tlvs = substr($msg, 12);
    while ($tlvlen > 0) {
	my ($tlv, $len) = unpack("Cv", $tlvs);
	$ret->{'tlvs'}{$tlv} = substr($tlvs, 3, $len);
	$tlvlen -= $len + 3;
	$tlvs = substr($tlvs, $len + 3);
    }

    # success only if TLV 0x02 is 0 and TLV 0x01 exists
    return undef unless ((unpack("V", $ret->{'tlvs'}{0x02}) == 0) && exists($ret->{'tlvs'}{0x01}));

    # decode the list of supported systems in TLV 0x01
    my $data = $ret->{'tlvs'}{0x01};
    my $n = unpack("C", $data);
    $data = substr($data, 1);
    print "supports $n QMI subsystems:\n";
    for (my $i = 0; $i < $n; $i++) {
	my ($sys, $maj, $min) = unpack("Cvv", $data);
	my $system = $sysname{$sys} || sprintf("%#04x", $sys);
	print "  $system ($maj.$min)\n";
	$data = substr($data, 5);
    }

    return 1;
}


# send CDC encapsulated message to interface
sub send_msg {
    my ($dev, $ifnum, $msg, $len) = @_;
    my $ret = $dev->control_msg(0x21,   # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
				0,      # CDC SEND_ENCAPSULATED_COMMAND
				0,      # zero
				$ifnum, # wIndex = interface
				$msg,
				$len,
				1000);  # timeout in ms

    warn "control_msg() returned $ret\n" if $opt{'debug'};
    if ($ret != $len) {
	warn "control_msg() failed ($ret): $!\n";
    }
    return $ret;
}

# recv CDC encapsulated message to interface
sub recv_msg {
    my ($dev, $ifnum) = @_;
    my $buf = 0 x 512; # pre-allocate buffer - libusb is not perl!
    my $ret = $dev->control_msg(0xa1,   # USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE
				1,      # CDC GET_ENCAPSULATED_RESPONSE
				0,      # zero
				$ifnum, # wIndex = interface
				$buf,
				512,
				1000);  # timeout in ms
    warn "control_msg() returned $ret\n" if $opt{'debug'};
    return ($ret, $buf);
}


sub usage {
    warn <<EOT
      Usage:
	$0 [--debug] --device=idVendor:idProduct [--if=bInterfaceNumber]

      Options:
	--debug
	    debug output
	--device
	    USB device ID to test - required
	--if
	    USB interface number


      Example:
	$0 --device=1199:68a2

      Note:
	This script will unbind any driver from the interface(s)
	before testing. It will not rebind after finishing.  The
	simplest way to reset is to unplug and replug the
	device.

EOT
;
    exit 0;
}

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux