File Coverage

blib/lib/USB/Descriptor/Device.pm
Criterion Covered Total %
statement 15 150 10.0
branch 0 68 0.0
condition 0 9 0.0
subroutine 5 25 20.0
pod 18 18 100.0
total 38 270 14.0


line stmt bran cond sub pod time code
1             package USB::Descriptor::Device;
2              
3 1     1   12 use strict;
  1         2  
  1         34  
4 1     1   5 use warnings;
  1         2  
  1         23  
5 1     1   1470 use USB::Descriptor::Configuration;
  1         3  
  1         304  
6              
7             our $VERSION = '2'; # Bump this when the interface changes
8              
9              
10 1     1   21 use overload '@{}' => \&bytes;
  1         3  
  1         9  
11              
12 1         2319 use constant fields => qw(
13             bLength bDescriptorType bcdUSB bDeviceClass bDeviceSubClass bDeviceProtocol
14             bMaxPacketSize idVendor idProduct bcdDevice iManufacturer iProduct
15             iSerialNumber bNumConfigurations
16 1     1   84 );
  1         2  
17              
18             =head1 NAME
19              
20             USB::Descriptor::Device - USB Device Descriptor
21              
22             =head1 SYNOPSIS
23              
24             An object representation of a USB device descriptor.
25              
26             use USB::Descriptor::Device;
27              
28             my $device = USB::Descriptor::Device->new( product => 'My First Device' );
29             $device->vendorID(0x1234);
30             $device->productID(0x5678);
31             $device->configurations( [ USB::Descriptor::Configuration->new() ] );
32             ...
33              
34             =head1 DESCRIPTION
35              
36             L represents a USB device descriptor. After creating
37             and configuring an instanace of L, arrayification (or
38             C<< $device->bytes >>) yeilds an array of all of the values that comprise the fields
39             of a USB Device Descriptor structure given the configured settings. The
40             resulting array can then be used to generate the structures (in Assembly or
41             C or...) necessary for building the firmware of the described device.
42              
43             After adding one or more L objects to an
44             instance of L, it can be used to generate USB
45             Configuration Descriptors. Arrayifying each child descriptor in the
46             configurations array yields the appropriate descriptor bytes, including
47             interfaces and endpoints.
48              
49             Strings specificed for the device descriptor (manufacturer, product or serial
50             number) as well as the strings for child descriptors (configuration,
51             interface, etc) will be automatically indexed by L and
52             the proper indexes embedded in the appropriate descriptors during arrayification.
53              
54             After arrayifying the L and all child
55             Ls, the generated set of strings can be
56             retrieved (in index order) by calling the 'strings' method.
57              
58             =head1 CONSTRUCTOR
59              
60             =over
61              
62             =item $device = USB::Descriptor::Device->new(vendorID=>$vendorID, ...);
63              
64             Constructs and returns a new L object using the
65             passed options. Each option key is the name of an accessor method.
66              
67             =back
68              
69             =cut
70              
71             sub new
72             {
73 0     0 1   my ($this, %options) = @_;
74 0   0       my $class = ref($this) || $this;
75              
76             # Set defaults
77 0           my $self = {
78             'bcdUSB' => 0x0200, # USB 2.0.0
79             'bDeviceClass' => 0, # Composite
80             'bDeviceSubClass' => 0, # Composite
81             'bDeviceProtocol' => 0, # Composite
82             'bMaxPacketSize' => 8, # Low speed device
83             'vendorID' => 0, # Invalid
84             'productID' => 0, # Invalid
85             'bcdDevice' => 0, # Device version 0.0.0
86             'strings' => {},
87             };
88 0           bless $self, $class;
89              
90 0           while( my ($key, $value) = each %options )
91             {
92 0           $self->$key($value);
93             }
94              
95 0           return $self;
96             }
97              
98             =head1 Arrayification
99              
100             =over
101              
102             =item $device->bytes (or @{$device} )
103              
104             Returns an array of bytes containing all of the fields in the device
105             descriptor fields, but not including configuration descriptors.
106              
107             =back
108              
109             =cut
110              
111             sub bytes
112             {
113 0     0 1   my $s = shift;
114              
115 0           my @bytes;
116              
117 0           push @bytes, 0x12; # Device descriptors are 18 bytes long
118 0           push @bytes, 0x01; # bDescriptorType
119 0           push @bytes, $s->bcdUSB & 0xFF; # bcdUSB low
120 0           push @bytes, ($s->bcdUSB >> 8) & 0xFF; # bcdUSB high
121 0           push @bytes, $s->class; # bDeviceClass
122 0           push @bytes, $s->subclass; # bDeviceSubClass
123 0           push @bytes, $s->protocol; # bDeviceProtocol
124 0           push @bytes, $s->max_packet_size; # bMaxPacketSize
125 0           push @bytes, $s->vendorID & 0xFF; # idVendor low
126 0           push @bytes, ($s->vendorID >> 8) & 0xFF; # idVendor high
127 0           push @bytes, $s->productID & 0xFF; # idProduct low
128 0           push @bytes, ($s->productID >> 8) & 0xFF; # idProduct high
129 0           push @bytes, $s->bcdDevice & 0xFF; # bcdDevice low
130 0           push @bytes, ($s->bcdDevice >> 8) & 0xFF; # bcdDevice high
131              
132             # Make string descriptor indices
133 0           push @bytes, $s->_index_for_string($s->manufacturer); # iManufacturer
134 0           push @bytes, $s->_index_for_string($s->product); # iProduct
135 0           push @bytes, $s->_index_for_string($s->serial_number); # iSerialNumber
136              
137 0 0         my $numConfigurations = $s->{'configurations'} ? @{$s->{'configurations'}} : 0;
  0            
138 0           push @bytes, $numConfigurations; # bNumConfigurations
139              
140             # Check that all of the configurations have a valid bConfigurationValue
141             # Assign them sequentially for any that don't
142 0           my $i = 0;
143 0           for( @{$s->{'configurations'}} )
  0            
144             {
145             # Set the configuration value if it hasn't already been set
146 0 0         $_->value($i++) if $_->value <= $i; # Use <= to force update of $i
147              
148             # Update $i if the interface already has a higher number
149 0 0         $i = $_->value if $_->value > $i;
150             }
151              
152 0 0         print "Device descriptor length is wrong" unless $bytes[0] == scalar @bytes;
153              
154 0           return \@bytes;
155             }
156              
157             =head1 ATTRIBUTES
158              
159             =over
160              
161             =item $interface->bcdDevice
162              
163             Direct access to the bcdDevice value. Don't use this unless you know what you're
164             doing.
165              
166             =item $interface->bcdUSB
167              
168             Direct access to the bcdUSB value. Don't use this unless you know what you're
169             doing.
170              
171             =item $device->class
172              
173             Get/Set the device class code (bDeviceClass).
174              
175             =item $interface->configuration
176              
177             A convenience method that wraps a single hash reference in an array and passes
178             it to C.
179              
180             =item $device->configurations
181              
182             Get/Set the array of L objects.
183              
184             =item $device->manufacturer
185              
186             Get/Set the device's manufacturer string. A string descriptor index
187             (iManufacturer) will be automatically assigned during arrayification.
188              
189             =item $device->max_packet_size
190              
191             Get/Set the maximum packet size for endpoint 0 (bMaxPacketSize). Valid values
192             are 8, 16, 32, 64. Defaults to 8.
193              
194             =item $device->product
195              
196             Get/Set the device's product string. A string descriptor index
197             (iProduct) will be automatically assigned during arrayification.
198              
199             =item $device->productID
200              
201             Get/Set the device's Product ID (idProduct).
202              
203             =item $device->protocol
204              
205             Get/Set the device's protocol (bDeviceProtocol).
206              
207             =item $device->serial_number
208              
209             Get/Set the device's serial number string. A string descriptor index
210             (iSerialNumber) will be automatically assigned during arrayification.
211              
212             =item $device->strings
213              
214             Returns an array of strings in index order from the string descriptor set.
215              
216             =item $device->subclass
217              
218             Get/Set the device's SubClass (bDeviceSubClass).
219              
220             =item $device->usb_version
221              
222             Get/Set the supported USB version (bcdUSB). The version is specified as a dotted
223             string. eg. '1.2.3'. Defaults to '2.0.0'.
224              
225             =item $device->vendorID
226              
227             Get/Set the device's Vendor ID (idVendor).
228              
229             =item $device->version
230              
231             Get/Set the device's version number (bcdDevice). The version is specified as a
232             dotted string. eg. '1.2.3'.
233              
234             =back
235              
236             =cut
237              
238             sub bcdUSB
239             {
240 0     0 1   my $s = shift;
241 0 0         $s->{'bcdUSB'} = int(shift) & 0xFFFF if scalar @_;
242 0           $s->{'bcdUSB'};
243             }
244              
245             sub class
246             {
247 0     0 1   my $s = shift;
248 0 0         $s->{'bDeviceClass'} = int(shift) & 0xFF if scalar @_;
249 0           $s->{'bDeviceClass'};
250             }
251              
252             sub subclass
253             {
254 0     0 1   my $s = shift;
255 0 0         $s->{'bDeviceSubClass'} = int(shift) & 0xFF if scalar @_;
256 0           $s->{'bDeviceSubClass'};
257             }
258              
259             sub protocol
260             {
261 0     0 1   my $s = shift;
262 0 0         $s->{'bDeviceProtocol'} = int(shift) & 0xFF if scalar @_;
263 0           $s->{'bDeviceProtocol'};
264             }
265              
266             sub max_packet_size
267             {
268 0     0 1   my $s = shift;
269 0 0         $s->{'bMaxPacketSize'} = int(shift) & 0xFF if scalar @_;
270 0           $s->{'bMaxPacketSize'};
271             }
272              
273             sub vendorID
274             {
275 0     0 1   my $s = shift;
276 0 0         $s->{'vendorID'} = int(shift) & 0xFFFF if scalar @_;
277 0           $s->{'vendorID'};
278             }
279              
280             sub productID
281             {
282 0     0 1   my $s = shift;
283 0 0         $s->{'productID'} = int(shift) & 0xFFFF if scalar @_;
284 0           $s->{'productID'};
285             }
286              
287             sub bcdDevice
288             {
289 0     0 1   my $s = shift;
290 0 0         $s->{'bcdDevice'} = int(shift) & 0xFFFF if scalar @_;
291 0           $s->{'bcdDevice'};
292             }
293              
294             sub _sanitize_bcd_array
295             {
296 0     0     my @v = @_;
297 0           @v = map(int, @v); # Force integers
298 0 0         @v = $v[0..2] if 3 < scalar @v; # Limit the array to three elements
299 0           push @v, 0 while scalar(@v) < 3; # Append any missing trailing zeros
300              
301             # Mask out overly large numbers
302 0           $v[0] = $v[0] & 0xFF;
303 0           @v[1..2] = map { $_ & 0x0F } @v[1..2];
  0            
304              
305 0           return @v;
306             }
307              
308             # Pass a dotted string or an array
309             # Returns a string in scalar context and an array in list context
310             sub usb_version
311             {
312 0     0 1   my $s = shift;
313 0 0         if( scalar @_ )
314             {
315 0           my @v;
316             # Parse string arguments, otherwise hope that the argument is an array
317 0 0         if( 1 == scalar @_ )
318             {
319 0           @v = split /\./, shift;
320             }
321             else
322             {
323 0           @v = @_;
324             }
325 0           @v = _sanitize_bcd_array(@v);
326              
327 0           $s->{'bcdUSB'} = ($v[0] << 8) | ($v[1] << 4) | $v[2];
328 0           $s->{'usb_version'} = \@v;
329             }
330 0 0         wantarray ? @{$s->{'usb_version'}} : join('.',@{$s->{'usb_version'}});
  0            
  0            
331             }
332              
333             sub version
334             {
335 0     0 1   my $s = shift;
336 0 0         if( scalar @_ )
337             {
338 0           my @v;
339             # Parse string arguments, otherwise hope that the argument is an array
340 0 0         if( 1 == scalar @_ )
341             {
342 0           @v = split /\./, shift;
343             }
344             else
345             {
346 0           @v = @_;
347             }
348 0           @v = _sanitize_bcd_array(@v);
349              
350 0           $s->{'bcdDevice'} = ($v[0] << 8) | ($v[1] << 4) | $v[2];
351 0           $s->{'device_version'} = \@v;
352             }
353 0 0         wantarray ? @{$s->{'device_version'}} : join('.',@{$s->{'device_version'}});
  0            
  0            
354             }
355              
356             sub configuration
357             {
358 0     0 1   my $s = shift;
359 0 0 0       $s->configurations([$_[0]]) if( scalar(@_) and (ref($_[0]) eq 'HASH') );
360 0           $s->{'configurations'}[0];
361             }
362              
363             sub configurations
364             {
365 0     0 1   my $s = shift;
366 0 0         if( scalar @_ )
367             {
368 0 0         if( ref($_[0]) eq 'ARRAY' )
    0          
369             {
370             # Convert hash reference arguments into Configuration objects
371             my @configurations = map
372             {
373 0 0         if( ref($_) eq 'HASH' ) # Hash reference?
  0 0          
374             {
375 0           USB::Descriptor::Configuration->new(%{$_});
  0            
376             }
377             elsif( ref($_) ) # Reference to something else?
378             {
379 0           $_; # Use it
380             }
381 0           } @{$_[0]};
382 0           $s->{'configurations'} = \@configurations;
383              
384             # Reparent the new configuration descriptors
385 0           $_->_parent($s) for @{$s->{'configurations'}};
  0            
386             }
387             elsif( ref($_[0]) eq 'HASH' )
388             {
389             # If a hash reference was passed, let configuration() handle it
390 0           $s->configuration($_[0]);
391             }
392             }
393 0           $s->{'configurations'};
394             }
395              
396             # String descriptors
397              
398             sub manufacturer
399             {
400 0     0 1   my $s = shift;
401 0 0         $s->{'manufacturer'} = shift if scalar @_;
402 0           $s->{'manufacturer'};
403             }
404              
405             sub product
406             {
407 0     0 1   my $s = shift;
408 0 0         $s->{'product'} = shift if scalar @_;
409 0           $s->{'product'};
410             }
411              
412             sub serial_number
413             {
414 0     0 1   my $s = shift;
415 0 0         $s->{'serial_number'} = shift if scalar @_;
416 0           $s->{'serial_number'};
417             }
418              
419             # In list context, returns the array of string descriptors
420             # In scalar context, returns the number of string descriptors
421             sub strings
422             {
423 0     0 1   my $s = shift;
424 0           my @strings;
425              
426 0 0         push @strings, $s->manufacturer if $s->manufacturer; # Manufacturer
427 0 0         push @strings, $s->product if $s->product; # Product
428 0 0         push @strings, $s->serial_number if $s->serial_number; # Serial number
429             # Walk configurations...
430              
431 0           return sort { $s->{'strings'}{$a} <=> $s->{'strings'}{$b} } keys %{$s->{'strings'}};
  0            
  0            
432             }
433              
434             sub _index_for_string
435             {
436 0     0     my ($s, $string) = @_;
437 0 0 0       if( defined($string) and length($string) )
438             {
439             # Return the string's index if it's already known
440 0 0         return $s->{'strings'}{$string} if $s->{'strings'}{$string};
441              
442             # Otherwise, create a new index for it
443 0           my $max = (sort values %{$s->{'strings'}})[-1];
  0            
444 0 0         $max = 0 unless $max;
445              
446             # Assign the string an index one higher than the current highest
447 0           $s->{'strings'}->{$string} = $max+1;
448 0           return $s->{'strings'}->{$string};
449             }
450 0           return 0;
451             }
452              
453             1;
454              
455             =head1 AUTHOR
456              
457             Brandon Fosdick, C<< >>
458              
459              
460             =head1 BUGS
461              
462             Please report any bugs or feature requests to C, or through
463             the web interface at L. I will be notified, and then you'll
464             automatically be notified of progress on your bug as I make changes.
465              
466              
467             =head1 SUPPORT
468              
469             You can find documentation for this module with the perldoc command.
470              
471             perldoc USB::Descriptor::Device
472              
473              
474             You can also look for information at:
475              
476             =over 4
477              
478             =item * RT: CPAN's request tracker (report bugs here)
479              
480             L
481              
482             =item * AnnoCPAN: Annotated CPAN documentation
483              
484             L
485              
486             =item * CPAN Ratings
487              
488             L
489              
490             =item * Search CPAN
491              
492             L
493              
494             =back
495              
496              
497             =head1 ACKNOWLEDGEMENTS
498              
499              
500             =head1 LICENSE AND COPYRIGHT
501              
502             Copyright 2011 Brandon Fosdick.
503              
504             This program is released under the terms of the BSD License.
505              
506             =cut