Browse Source

usb: descriptor generator tool

Descriptor generator tool. Not yet being used, but a configuration
file that should match the current USB configuration included.
H. Peter Anvin 3 years ago
parent
commit
dd02802dcb
4 changed files with 1108 additions and 0 deletions
  1. 92 0
      fpga/usb/usb_desc.conf
  2. 23 0
      tools/perlinc/langid.ph
  3. 462 0
      tools/perlinc/lcid.ph
  4. 531 0
      tools/usbdescgen.pl

+ 92 - 0
fpga/usb/usb_desc.conf

@@ -0,0 +1,92 @@
+# -*- perl -*-
+#
+# Invoked from usbdescgen.pl
+#
+
+usb_languages('en_US', 'sv_SE');
+
+my $vendor_id    = 0x1d50;
+my $device_id    = 0x6149;
+my $version_id   = 0x0100;
+
+my $manufacturer = usb_string('' => 'Peter & Per');
+my $product      = usb_string('' => 'MAX80');
+my $serial       = usb_string('' => ('?' x 16));
+
+my $mgmt_if, $data_if;
+
+usb_device {
+    usb_desc('device',
+	     word(0x101),		# USB version
+	     usb_class('cdc'),		# Communications device class
+	     byte(8),			# Max packet size on endpoint 0
+	     word($vendor_id), word($device_id), word($version_id),
+	     $manufacturer, $product, $serial,
+	     usb_children		# Configuration count
+	),
+
+	usb_dset {
+	    usb_desc('configuration',
+		     usb_totallen,
+		     usb_children,	 # Number of interfaces
+		     usb_index,		 # Configuration index
+		     usb_string(),	 # Text description (empty)
+		     byte(0xc0),	 # Self or bus powered
+		     byte(500 >> 1)),	 # Up to 500 mA
+
+		# Management interface
+		usb_dset {
+		    usb_desc('interface',
+			     $mgmt_if = usb_index,
+			     byte(0),	# No alternate settings
+			     byte(1),	# Endpoint count
+			     usb_class('cdc','acm','v25ter'),
+			     usb_string()),
+
+			usb_desc('cs_interface.header',
+				 word(0x120)), # CDC spec version 1.20
+
+			usb_desc('cs_interface.call_management',
+				 byte(0x01), # No AT commands over data
+				 byte(0)),   # No interface applicable
+
+			usb_desc('cs_interface.acm',
+				 byte(0x04)), # Supports SEND_BREAK
+
+			usb_desc('cs_interface.union',
+				 byte(\$mgmt_if),  # Controlling interface
+				 byte(\$data_if)), # Data interface
+
+			# EP 3, input: notification
+			usb_desc('endpoint',
+				 ep_i(3),	# Endpoint 3 input
+				 byte(3),	# Interrupt, data
+				 word(64),	# Max packet size
+				 byte(2)),	# Interval
+	    },
+
+		# Data interface
+		usb_dset {
+		    usb_desc('interface',
+			     $data_if = usb_index,
+			     byte(0),	# No alternate settings
+			     byte(2),	# Endpoint count
+			     usb_class('cdc_data'),
+			     usb_string()),
+
+			# EP 1, output: downstream data
+			usb_desc('endpoint',
+				 ep_o(1),
+				 byte(2),	# Bulk, data
+				 word(64),	# Max packet size
+				 byte(0)),      # Interval
+
+			# EP 2, input: upstream data
+			usb_desc('endpoint',
+				 ep_i(2),
+				 byte(3),      # Bulk, data
+				 word(64),     # Max packet size
+				 byte(0))      # Interval
+	    }
+    }
+};

+ 23 - 0
tools/perlinc/langid.ph

@@ -0,0 +1,23 @@
+#!/usr/bin/perl
+
+use strict;
+use integer;
+
+our %langid;
+require 'lcid.ph';
+
+sub langid($) {
+    my($s) = @_;
+
+    $s = lc($s);
+    $s =~ s/\P{Alnum}+/-/g;
+
+    while ($s ne '') {
+	my $lcid = $langid{$s};
+	return $lcid if (defined($lcid));
+
+	last unless ($lcid =~ /(.*)\-[^-]+$/);
+	$lcid = $1;
+    }
+    return undef;
+}

+ 462 - 0
tools/perlinc/lcid.ph

@@ -0,0 +1,462 @@
+%langid = (
+	'af'            => 0x0036,
+	'af-za'         => 0x0436,
+	'am'            => 0x005e,
+	'am-et'         => 0x045e,
+	'any'           => 0x007f,
+	'ar'            => 0x0001,
+	'ar-145'        => 0x4801,
+	'ar-ae'         => 0x3801,
+	'ar-bh'         => 0x3c01,
+	'ar-dz'         => 0x1401,
+	'ar-eg'         => 0x0c01,
+	'ar-iq'         => 0x0801,
+	'ar-jo'         => 0x2c01,
+	'ar-kw'         => 0x3401,
+	'ar-lb'         => 0x3001,
+	'ar-ly'         => 0x1001,
+	'ar-ma'         => 0x1801,
+	'ar-om'         => 0x2001,
+	'ar-ploc-sa'    => 0x4401,
+	'ar-qa'         => 0x4001,
+	'ar-sa'         => 0x0401,
+	'ar-sy'         => 0x2801,
+	'ar-tn'         => 0x1c01,
+	'ar-ye'         => 0x2401,
+	'arn'           => 0x007a,
+	'arn-cl'        => 0x047a,
+	'as'            => 0x004d,
+	'as-in'         => 0x044d,
+	'az'            => 0x002c,
+	'az-cyrl'       => 0x742c,
+	'az-cyrl-az'    => 0x082c,
+	'az-latn'       => 0x782c,
+	'az-latn-az'    => 0x042c,
+	'ba'            => 0x006d,
+	'ba-ru'         => 0x046d,
+	'be'            => 0x0023,
+	'be-by'         => 0x0423,
+	'bg'            => 0x0002,
+	'bg-bg'         => 0x0402,
+	'bin'           => 0x0066,
+	'bin-ng'        => 0x0466,
+	'bn'            => 0x0045,
+	'bn-bd'         => 0x0845,
+	'bn-in'         => 0x0445,
+	'bo'            => 0x0051,
+	'bo-bt'         => 0x0851,
+	'bo-cn'         => 0x0451,
+	'br'            => 0x007e,
+	'br-fr'         => 0x047e,
+	'bs'            => 0x781a,
+	'bs-cyrl'       => 0x641a,
+	'bs-cyrl-ba'    => 0x201a,
+	'bs-latn'       => 0x681a,
+	'bs-latn-ba'    => 0x141a,
+	'ca'            => 0x0003,
+	'ca-es'         => 0x0403,
+	'ca-es-valencia' => 0x0803,
+	'chr'           => 0x005c,
+	'chr-cher'      => 0x7c5c,
+	'chr-cher-us'   => 0x045c,
+	'co'            => 0x0083,
+	'co-fr'         => 0x0483,
+	'cs'            => 0x0005,
+	'cs-cz'         => 0x0405,
+	'cy'            => 0x0052,
+	'cy-gb'         => 0x0452,
+	'da'            => 0x0006,
+	'da-dk'         => 0x0406,
+	'de'            => 0x0007,
+	'de-at'         => 0x0c07,
+	'de-ch'         => 0x0807,
+	'de-de'         => 0x0407,
+	'de-li'         => 0x1407,
+	'de-lu'         => 0x1007,
+	'defined'       => 0x0827,
+	'dsb'           => 0x7c2e,
+	'dsb-de'        => 0x082e,
+	'dv'            => 0x0065,
+	'dv-mv'         => 0x0465,
+	'dz-bt'         => 0x0c51,
+	'el'            => 0x0008,
+	'el-gr'         => 0x0408,
+	'en'            => 0x0009,
+	'en-029'        => 0x2409,
+	'en-ae'         => 0x4c09,
+	'en-au'         => 0x0c09,
+	'en-bh'         => 0x5009,
+	'en-bz'         => 0x2809,
+	'en-ca'         => 0x1009,
+	'en-eg'         => 0x5409,
+	'en-gb'         => 0x0809,
+	'en-hk'         => 0x3c09,
+	'en-id'         => 0x3809,
+	'en-ie'         => 0x1809,
+	'en-in'         => 0x4009,
+	'en-jm'         => 0x2009,
+	'en-jo'         => 0x5809,
+	'en-kw'         => 0x5c09,
+	'en-my'         => 0x4409,
+	'en-nz'         => 0x1409,
+	'en-ph'         => 0x3409,
+	'en-sg'         => 0x4809,
+	'en-tr'         => 0x6009,
+	'en-tt'         => 0x2c09,
+	'en-us'         => 0x0409,
+	'en-ye'         => 0x6409,
+	'en-za'         => 0x1c09,
+	'en-zw'         => 0x3009,
+	'es'            => 0x000a,
+	'es-419'        => 0x580a,
+	'es-ar'         => 0x2c0a,
+	'es-bo'         => 0x400a,
+	'es-cl'         => 0x340a,
+	'es-co'         => 0x240a,
+	'es-cr'         => 0x140a,
+	'es-cu'         => 0x5c0a,
+	'es-do'         => 0x1c0a,
+	'es-ec'         => 0x300a,
+	'es-es'         => 0x0c0a,
+	'es-es_tradnl'  => 0x040a,
+	'es-gt'         => 0x100a,
+	'es-hn'         => 0x480a,
+	'es-mx'         => 0x080a,
+	'es-ni'         => 0x4c0a,
+	'es-pa'         => 0x180a,
+	'es-pe'         => 0x280a,
+	'es-pr'         => 0x500a,
+	'es-py'         => 0x3c0a,
+	'es-sv'         => 0x440a,
+	'es-us'         => 0x540a,
+	'es-uy'         => 0x380a,
+	'es-ve'         => 0x200a,
+	'et'            => 0x0025,
+	'et-ee'         => 0x0425,
+	'eu'            => 0x002d,
+	'eu-es'         => 0x042d,
+	'fa'            => 0x0029,
+	'fa-ir'         => 0x0429,
+	'ff'            => 0x0067,
+	'ff-latn'       => 0x7c67,
+	'ff-latn-ng'    => 0x0467,
+	'ff-latn-sn'    => 0x0867,
+	'ff-ng'         => 0x0467,
+	'fi'            => 0x000b,
+	'fi-fi'         => 0x040b,
+	'fil'           => 0x0064,
+	'fil-ph'        => 0x0464,
+	'fo'            => 0x0038,
+	'fo-fo'         => 0x0438,
+	'fr'            => 0x000c,
+	'fr-015'        => 0xe40c,
+	'fr-029'        => 0x1c0c,
+	'fr-be'         => 0x080c,
+	'fr-ca'         => 0x0c0c,
+	'fr-cd'         => 0x240c,
+	'fr-ch'         => 0x100c,
+	'fr-ci'         => 0x300c,
+	'fr-cm'         => 0x2c0c,
+	'fr-fr'         => 0x040c,
+	'fr-ht'         => 0x3c0c,
+	'fr-lu'         => 0x140c,
+	'fr-ma'         => 0x380c,
+	'fr-mc'         => 0x180c,
+	'fr-ml'         => 0x340c,
+	'fr-re'         => 0x200c,
+	'fr-sn'         => 0x280c,
+	'fy'            => 0x0062,
+	'fy-nl'         => 0x0462,
+	'ga'            => 0x003c,
+	'ga-ie'         => 0x083c,
+	'gd'            => 0x0091,
+	'gd-gb'         => 0x0491,
+	'gl'            => 0x0056,
+	'gl-es'         => 0x0456,
+	'gn'            => 0x0074,
+	'gn-py'         => 0x0474,
+	'gsw'           => 0x0084,
+	'gsw-fr'        => 0x0484,
+	'gu'            => 0x0047,
+	'gu-in'         => 0x0447,
+	'ha'            => 0x0068,
+	'ha-latn'       => 0x7c68,
+	'ha-latn-ng'    => 0x0468,
+	'haw'           => 0x0075,
+	'haw-us'        => 0x0475,
+	'he'            => 0x000d,
+	'he-il'         => 0x040d,
+	'hi'            => 0x0039,
+	'hi-in'         => 0x0439,
+	'hid'           => 0x00ff,
+	'hr'            => 0x001a,
+	'hr-ba'         => 0x101a,
+	'hr-hr'         => 0x041a,
+	'hsb'           => 0x002e,
+	'hsb-de'        => 0x042e,
+	'hu'            => 0x000e,
+	'hu-hu'         => 0x040e,
+	'hy'            => 0x002b,
+	'hy-am'         => 0x042b,
+	'ibb'           => 0x0069,
+	'ibb-ng'        => 0x0469,
+	'id'            => 0x0021,
+	'id-id'         => 0x0421,
+	'ig'            => 0x0070,
+	'ig-ng'         => 0x0470,
+	'ii'            => 0x0078,
+	'ii-cn'         => 0x0478,
+	'is'            => 0x000f,
+	'is-is'         => 0x040f,
+	'it'            => 0x0010,
+	'it-ch'         => 0x0810,
+	'it-it'         => 0x0410,
+	'iu'            => 0x005d,
+	'iu-cans'       => 0x785d,
+	'iu-cans-ca'    => 0x045d,
+	'iu-latn'       => 0x7c5d,
+	'iu-latn-ca'    => 0x085d,
+	'ja'            => 0x0011,
+	'ja-jp'         => 0x0411,
+	'ja-ploc-jp'    => 0x0811,
+	'ka'            => 0x0037,
+	'ka-ge'         => 0x0437,
+	'khb-talu-cn'   => 0x0490,
+	'kk'            => 0x003f,
+	'kk-cyrl'       => 0x783f,
+	'kk-kz'         => 0x043f,
+	'kk-latn'       => 0x7c3f,
+	'kk-latn-kz'    => 0x083f,
+	'kl'            => 0x006f,
+	'kl-gl'         => 0x046f,
+	'km'            => 0x0053,
+	'km-kh'         => 0x0453,
+	'kn'            => 0x004b,
+	'kn-in'         => 0x044b,
+	'ko'            => 0x0012,
+	'ko-kr'         => 0x0412,
+	'kok'           => 0x0057,
+	'kok-in'        => 0x0457,
+	'kr'            => 0x0071,
+	'kr-latn-ng'    => 0x0471,
+	'ks'            => 0x0060,
+	'ks-arab'       => 0x0460,
+	'ks-deva-in'    => 0x0860,
+	'ku'            => 0x0092,
+	'ku-arab'       => 0x7c92,
+	'ku-arab-iq'    => 0x0492,
+	'ky'            => 0x0040,
+	'ky-kg'         => 0x0440,
+	'la'            => 0x0076,
+	'la-va'         => 0x0476,
+	'lb'            => 0x006e,
+	'lb-lu'         => 0x046e,
+	'lo'            => 0x0054,
+	'lo-la'         => 0x0454,
+	'lt'            => 0x0027,
+	'lt-lt'         => 0x0427,
+	'lv'            => 0x0026,
+	'lv-lv'         => 0x0426,
+	'mi'            => 0x0081,
+	'mi-nz'         => 0x0481,
+	'mk'            => 0x002f,
+	'mk-mk'         => 0x042f,
+	'ml'            => 0x004c,
+	'ml-in'         => 0x044c,
+	'mn'            => 0x0050,
+	'mn-cyrl'       => 0x7850,
+	'mn-mn'         => 0x0450,
+	'mn-mong'       => 0x7c50,
+	'mn-mong-cn'    => 0x0850,
+	'mn-mong-mn'    => 0x0c50,
+	'mni'           => 0x0058,
+	'mni-in'        => 0x0458,
+	'moh'           => 0x007c,
+	'moh-ca'        => 0x047c,
+	'mr'            => 0x004e,
+	'mr-in'         => 0x044e,
+	'ms'            => 0x003e,
+	'ms-bn'         => 0x083e,
+	'ms-my'         => 0x043e,
+	'mt'            => 0x003a,
+	'mt-mt'         => 0x043a,
+	'my'            => 0x0055,
+	'my-mm'         => 0x0455,
+	'nb'            => 0x7c14,
+	'nb-no'         => 0x0414,
+	'ne'            => 0x0061,
+	'ne-in'         => 0x0861,
+	'ne-np'         => 0x0461,
+	'neither'       => 0x0827,
+	'nl'            => 0x0013,
+	'nl-be'         => 0x0813,
+	'nl-nl'         => 0x0413,
+	'nn'            => 0x7814,
+	'nn-no'         => 0x0814,
+	'no'            => 0x0014,
+	'nor'           => 0x0827,
+	'nso'           => 0x006c,
+	'nso-za'        => 0x046c,
+	'oc'            => 0x0082,
+	'oc-fr'         => 0x0482,
+	'om'            => 0x0072,
+	'om-et'         => 0x0472,
+	'or'            => 0x0048,
+	'or-in'         => 0x0448,
+	'pa'            => 0x0046,
+	'pa-arab'       => 0x7c46,
+	'pa-arab-pk'    => 0x0846,
+	'pa-in'         => 0x0446,
+	'pap'           => 0x0079,
+	'pap-029'       => 0x0479,
+	'pl'            => 0x0015,
+	'pl-pl'         => 0x0415,
+	'plt-mg'        => 0x048d,
+	'prs'           => 0x008c,
+	'prs-af'        => 0x048c,
+	'ps'            => 0x0063,
+	'ps-af'         => 0x0463,
+	'pt'            => 0x0016,
+	'pt-br'         => 0x0416,
+	'pt-pt'         => 0x0816,
+	'qps-ploc'      => 0x0501,
+	'qps-ploca'     => 0x05fe,
+	'qps-plocm'     => 0x09ff,
+	'quc'           => 0x0093,
+	'quc-co'        => 0x0493,
+	'qut'           => 0x0086,
+	'qut-gt'        => 0x0486,
+	'quz'           => 0x006b,
+	'quz-bo'        => 0x046b,
+	'quz-ec'        => 0x086b,
+	'quz-pe'        => 0x0c6b,
+	'rm'            => 0x0017,
+	'rm-ch'         => 0x0417,
+	'ro'            => 0x0018,
+	'ro-md'         => 0x0818,
+	'ro-ro'         => 0x0418,
+	'ru'            => 0x0019,
+	'ru-md'         => 0x0819,
+	'ru-ru'         => 0x0419,
+	'rw'            => 0x0087,
+	'rw-rw'         => 0x0487,
+	'sa'            => 0x004f,
+	'sa-in'         => 0x044f,
+	'sah'           => 0x0085,
+	'sah-ru'        => 0x0485,
+	'sd'            => 0x0059,
+	'sd-arab'       => 0x7c59,
+	'sd-arab-pk'    => 0x0859,
+	'sd-deva-in'    => 0x0459,
+	'se'            => 0x003b,
+	'se-fi'         => 0x0c3b,
+	'se-no'         => 0x043b,
+	'se-se'         => 0x083b,
+	'si'            => 0x005b,
+	'si-lk'         => 0x045b,
+	'sk'            => 0x001b,
+	'sk-sk'         => 0x041b,
+	'sl'            => 0x0024,
+	'sl-si'         => 0x0424,
+	'sma'           => 0x783b,
+	'sma-no'        => 0x183b,
+	'sma-se'        => 0x1c3b,
+	'smj'           => 0x7c3b,
+	'smj-no'        => 0x103b,
+	'smj-se'        => 0x143b,
+	'smn'           => 0x703b,
+	'smn-fi'        => 0x243b,
+	'sms'           => 0x743b,
+	'sms-fi'        => 0x203b,
+	'so'            => 0x0077,
+	'so-so'         => 0x0477,
+	'sq'            => 0x001c,
+	'sq-al'         => 0x041c,
+	'sr'            => 0x7c1a,
+	'sr-cyrl'       => 0x6c1a,
+	'sr-cyrl-ba'    => 0x1c1a,
+	'sr-cyrl-cs'    => 0x0c1a,
+	'sr-cyrl-me'    => 0x301a,
+	'sr-cyrl-rs'    => 0x281a,
+	'sr-latn'       => 0x701a,
+	'sr-latn-ba'    => 0x181a,
+	'sr-latn-cs'    => 0x081a,
+	'sr-latn-me'    => 0x2c1a,
+	'sr-latn-rs'    => 0x241a,
+	'st'            => 0x0030,
+	'st-za'         => 0x0430,
+	'sv'            => 0x001d,
+	'sv-fi'         => 0x081d,
+	'sv-se'         => 0x041d,
+	'sw'            => 0x0041,
+	'sw-ke'         => 0x0441,
+	'syr'           => 0x005a,
+	'syr-sy'        => 0x045a,
+	'ta'            => 0x0049,
+	'ta-in'         => 0x0449,
+	'ta-lk'         => 0x0849,
+	'tdd-tale-cn'   => 0x048f,
+	'te'            => 0x004a,
+	'te-in'         => 0x044a,
+	'tg'            => 0x0028,
+	'tg-cyrl'       => 0x7c28,
+	'tg-cyrl-tj'    => 0x0428,
+	'th'            => 0x001e,
+	'th-th'         => 0x041e,
+	'ti'            => 0x0073,
+	'ti-er'         => 0x0873,
+	'ti-et'         => 0x0473,
+	'tk'            => 0x0042,
+	'tk-tm'         => 0x0442,
+	'tmz-ma'        => 0x0c5f,
+	'tn'            => 0x0032,
+	'tn-bw'         => 0x0832,
+	'tn-za'         => 0x0432,
+	'tr'            => 0x001f,
+	'tr-tr'         => 0x041f,
+	'ts'            => 0x0031,
+	'ts-za'         => 0x0431,
+	'tt'            => 0x0044,
+	'tt-ru'         => 0x0444,
+	'tzm'           => 0x005f,
+	'tzm-arab-ma'   => 0x045f,
+	'tzm-latn'      => 0x7c5f,
+	'tzm-latn-dz'   => 0x085f,
+	'tzm-tfng'      => 0x785f,
+	'tzm-tfng-ma'   => 0x105f,
+	'ug'            => 0x0080,
+	'ug-cn'         => 0x0480,
+	'uk'            => 0x0022,
+	'uk-ua'         => 0x0422,
+	'ur'            => 0x0020,
+	'ur-in'         => 0x0820,
+	'ur-pk'         => 0x0420,
+	'uz'            => 0x0043,
+	'uz-cyrl'       => 0x7843,
+	'uz-cyrl-uz'    => 0x0843,
+	'uz-latn'       => 0x7c43,
+	'uz-latn-uz'    => 0x0443,
+	've'            => 0x0033,
+	've-za'         => 0x0433,
+	'vi'            => 0x002a,
+	'vi-vn'         => 0x042a,
+	'wo'            => 0x0088,
+	'wo-sn'         => 0x0488,
+	'xh'            => 0x0034,
+	'xh-za'         => 0x0434,
+	'yi'            => 0x003d,
+	'yi-001'        => 0x043d,
+	'yo'            => 0x006a,
+	'yo-ng'         => 0x046a,
+	'zh'            => 0x7804,
+	'zh-cn'         => 0x0804,
+	'zh-hans'       => 0x0004,
+	'zh-hant'       => 0x7c04,
+	'zh-hk'         => 0x0c04,
+	'zh-mo'         => 0x1404,
+	'zh-sg'         => 0x1004,
+	'zh-tw'         => 0x0404,
+	'zh-yue-hk'     => 0x048e,
+	'zu'            => 0x0035,
+	'zu-za'         => 0x0435,
+);

+ 531 - 0
tools/usbdescgen.pl

@@ -0,0 +1,531 @@
+#!/usr/bin/perl
+#
+# Simple tool for generating a USB desciptor table or ROM/RAM
+#
+
+use integer;
+use strict;
+use File::Spec;
+use Encode;
+
+require 'langid.ph';
+
+# Descriptor types. This follows the document in which they were specified;
+# some USB documents specify in hex and others in decimal...
+our %DT = (
+    'device' => 1,
+    'configuration' => 2, 'config' => 2, 'conf' => 2,
+    'string' => 3, 'str' => '3',
+    'interface' => 4, 'if' => '4',
+    'endpoint' => 5, 'ep' => 5,
+    'device_qualifier' => 6, 'dq' => 6,
+    'other_speed_configuration' => 7, 'otherspeed' => 7, 'osc' => 7,
+    'interface_power' => 8, 'ifpwr' => 8,
+    'otg' => 9,
+    'debug' => 10,
+    'interface_association' => 11, 'iad' => 11,
+
+    'bos' => 15, 'binary_object_storage' => 15,
+    'device_capability' => 16, 'devcap' => 16,
+
+    'cs_interface' => 0x24, 'cs_if' => 0x24,
+    'cs_endpoint' => 0x25, 'cs_ep' => 0x25,
+
+    'superspeed_usb_endpoint_companion' => 48, 'usb_ep_companion' => 48,
+    'superspeedplus_isochronous_endpoint_companion' => 49, 'iso_ep_compation' => 49,
+    );
+
+# Descriptor subtypes, where applicable
+our %DS = (
+    # Under CS_INTERFACE
+    0x24 => {
+	'header' => 0x00,
+	    'call' => 0x01, 'call_management' => 0x01,
+	    'abstract_control' => 0x02, 'acm' => 0x02,
+	    'direct_line' => 0x03,
+	    'ringer' => 0x04,
+	    'line_state' => 0x05,
+	    'union' => 0x06,
+	    'country' => 0x07,
+	    'op_mode' => 0x08,
+	    'terminal' => 0x09,
+	    'net_channel' => 0x0a,
+	    'protocol_unit' => 0x0b,
+	    'extension_unit' => 0x0c,
+	    'multi_channel' => 0x0d,
+	    'capi' => 0x0e,
+	    'ethernet' => 0x0f,
+	    'atm' => 0x10,
+	    'whcm' => 0x11, 'wireless_handset' => 0x11,
+	    'mobile_line' => 0x12, 'mobile_direct_line' => 0x12,
+	    'mdlm_detail' => 0x13, 'mdlm' => 0x13,
+	    'device_management' => 0x14, 'device' => 0x14, 'mgmt' => 0x14,
+	    'command_set' => 0x16,
+	    'command_set_detail' => 0x17,
+	    'telephone_control' => 0x18, 'tcm' => 0x18, 'phone' => 0x18,
+	    'obex_service_identifier' => 0x19, 'obex' => 0x19
+    }
+    );
+
+#
+# Class, subclass, and protocol codes. Map a string to a number, then
+# use that number as an index to descend, if that entry exists.  0 is
+# the default; for some classes the subclass code is unused and so is
+# set to 0; no string to look up.
+#
+# Numbers below the class level are massively incomplete, feel free to add.
+#
+
+# Applies to all levels
+my %class_all_levels = (
+    undef => 0x00, 'undef' => 0x00, '-' => 0x00,
+    'none' => 0x00, 'default' => 0x00,
+    'vendor_specific' => 0xff, 'vendor' => 0xff,
+    );
+
+my $cdc_pstn_protocols = {
+    'v250' => 0x01, 'at' => 0x01, 'v25ter' => 0x01,
+	'pcca101' => 0x02,
+	'pcca101o' => 0x03, 'pcca' => 0x03,
+	'gsm707' => 0x04, 'gsm' => 0x04,
+	'3gpp2707' => 0x05, '3gpp' => 0x05, '3g' => 0x05,
+	'ca00170' => 0x06, 'tia_cdma' => 0x06, 'cdma' => 0x06
+};
+
+my %class_codes = (
+    'multi' => 0x00,		# Real class code in interface descriptors
+    'audio' => 0x01,
+    'cdc' => 0x02, 'communications' => 0x02,
+    0x02 => {
+	'dlcm' => 0x01, 'direct_line_control' => 0x01,
+	    'acm' => 0x02, 'abstract_control' => 0x02,
+	    0x02 => $cdc_pstn_protocols,
+	    'tcm' => 0x03, 'telephone_control' => 0x03,
+	    0x03 => $cdc_pstn_protocols,
+	    'mccm' => 0x04, 'multi_channel_control' => 0x04,
+	    0x04 => $cdc_pstn_protocols,
+	    'ccm' => 0x05, 'capi' => 0x05, 'capi_control' => 0x05,
+	    0x05 => $cdc_pstn_protocols,
+	    'ecm' => 0x06, 'ethernet_control' => 0x06,
+	    0x06 => $cdc_pstn_protocols,
+	    'atm' => 0x07, 'ancm' => 0x07, 'atm_networking_control' => 0x07,
+	    'whcm' => 0x08, 'wireless_handset_control' => 0x08,
+	    0x08 => $cdc_pstn_protocols,
+	    'device_management' => 0x09, 'mgmt' => 0x09, 'device' => 0x09,
+	    'mdlm' => 0x0a, 'mobile_direct_line' => 0x0a,
+	    'obex' => 0x0b,
+	    'eem' => 0x0c, 'ethernet' => 0x0c, 'ethernet_emulation' => 0x0c,
+	    0x0c => {
+		'ethernet' => 0x07, 'eem' => 0x07
+	},
+	    'ncm' => 0x0d, 'net' => 0x0d, 'network_control' => 0x0d,
+    },
+
+    'hid' => 0x03,
+
+    'physical' => 0x05,
+    'imaging' => 0x06, 'photo' => 0x06, 'idc' => 0x06,
+    'printer' => 0x07,
+    'mass_storage' => 0x08, 'storage' => 0x08, 'disk' => 0x08,
+    'hub' => 0x09,
+    'cdc_data' => 0x0a, 'data' => 0x0a,
+    0x0a => {
+	0 => {
+	    'ntb' => 0x01, 'network_transfer_block' => 0x01,
+
+		'isdn_bri' => 0x30, 'isdn' => 0x30,
+		'hdlc' => 0x31,
+		'transparent' => 0x32,
+
+		'q921_management' => 0x50, 'q921m' => 0x50,
+		'q921_datalink' => 0x51, 'q921' => 0x51,
+		'q921_tei_mux' => 0x52, 'q921tm' => 0x52,
+
+		'v42bis' => 0x90,
+		'euro_isdn' => 0x91, 'q931' => 0x91,
+		'v120' => 0x92, 'isdn_v24' => 0x92,
+		'capi' => 0x93,
+
+		'host' => 0xfd,
+		'external' => 0xfe,
+		'vendor' => 0xff
+	},
+    },
+    'smart_card' => 0x0b, 'smartcard' => 0x0b, 'scdc' => 0x0b,
+
+    'content_security' => 0x0d, 'drm' => 0x0d, 'csdc' => 0x0d,
+    'video' => 0x0e, 'vdc' => 0x0e,
+    'personal_healthcare' => 0x0f, 'healthcare' => 0x0f, 'health' => 0x0f, 'phdc' => 0x0f,
+    'audio_video' => 0x10, 'av' => 0x10, 'avdc' => 0x10,
+    'billboard' => 0x11, 'bdc' => 0x11,
+    'usb_c_bridge' => 0x12, 'usbc' => 0x12, 'usbcbdc' => 0x12,
+
+    'diagnostic' => 0xdc,
+    'wireless_controller' => 0xe0, 'wireless' => 0xe0,
+
+    'miscellaneous' => 0xef, 'misc' => 0xef,
+
+    'application_specific' => 0xfe, 'app_specific' => 0xfe, 'app' => 0xfe,
+    );
+
+my %packfmt = ( 1 => 'C', 2 => 'v', 4 => 'V', 8 => 'Q<' );
+
+my $utf16le = find_encoding('utf16le');
+
+sub atom($@) {
+    my $bytes = shift @_;
+    my @o = ();
+
+    foreach my $b (@_) {
+	my $t = ref $b;
+	if ($t eq 'SCALAR') {
+	    # To be resolved later
+	    push(@o, {'bytes' => $bytes, 'num' => $b});
+	} elsif ($t eq 'ARRAY') {
+	    push (@o, atom($bytes, @$b));
+	} elsif ($t eq 'HASH') {
+	    push(@o, $b);
+	} elsif ($t eq '') {
+	    push(@o, pack($packfmt{$bytes}, $b));
+	}
+    }
+    return @o;
+}
+
+sub byte(@) {
+    return atom(1,@_);
+}
+sub word(@) {
+    return atom(2,@_);
+}
+sub dword(@) {
+    return atom(4,@_);
+}
+sub qword(@) {
+    return atom(8,@_);
+}
+
+# Generate endpoint identifiers
+sub ep_i($) {
+    my($n) = @_;
+    return byte($n|0x80);
+}
+sub ep_o($) {
+    my($n) = @_;
+    return byte($n|0x00);
+}
+
+sub toint($) {
+    my($i) = @_;
+    return ($i =~ /^0/) ? oct $i : ($i =~ /^[1-9]/) ? $i+0 : undef;
+}
+
+my $err = 0;
+
+# Generate class code triplets
+sub usb_class($;$$) {
+    my @cl = @_;
+    my $lvl = \%class_codes;
+    my $cd = '';
+
+    while (scalar(@cl) < 3) {
+	push(@cl, undef);
+    }
+    while (scalar(@cl)) {
+	my $cs = shift(@cl);
+	my $cc = defined($cs) ? toint($cs) : 0;
+	if (!defined($cc)) {
+	    $cs = lc($cs);
+	    $cs =~ s/\P{Alnum}+/_/g;
+
+	    $cc = $lvl->{$cs} if (defined($lvl));
+	    $cc = $class_all_levels{$cs} unless (defined($cc));
+	    if (!defined($cc)) {
+		print STDERR "$0: unknown class code ", join('.', @_), "\n";
+		$err = 1;
+		$cc = 0;
+	    }
+
+	    $cd .= byte($cc);
+	    $lvl = $lvl->{$cc};
+	}
+    }
+    return $cd;
+}
+
+sub datalen(@) {
+    my $l = 0;
+
+    foreach my $b (@_) {
+	my $t = ref $b;
+	if ($t eq 'HASH') {
+	    $l += $b->{'bytes'};
+	} elsif ($t eq 'ARRAY') {
+	    $l += datalen(@$b);
+	} elsif ($t eq 'SCALAR') {
+	    $l += length($$b);
+	} elsif ($t eq '') {
+	    $l += length($b);
+	} else {
+	    die;
+	}
+    }
+
+    return $l;
+}
+
+sub makedata(@) {
+    my $o = '';
+
+    foreach my $b (@_) {
+	my $t = ref $b;
+	if ($t eq 'HASH') {
+	    if (defined($b->{'num'})) {
+		$o .= pack($packfmt{$b->{'bytes'}}, ${$b->{'num'}});
+	    } elsif (defined($b->{'data'})) {
+		my $raw;
+		$b->{'offs'} = length($o);
+		$raw = makedata($b->{'data'});
+		$b->{'raw'} = $raw;
+		$b->{'bytes'} = length($raw);
+		$o .= $raw;
+	    } else {
+		die;
+	    }
+	} elsif ($t eq 'ARRAY') {
+	    $o .= makedata(@$b);
+	} elsif ($t eq 'SCALAR') {
+	    $o .= makedata($$b);
+	} elsif ($t eq '') {
+	    $o .= $b;
+	} else {
+	    die;
+	}
+    }
+
+    return $o;
+}
+
+# USB descriptor set
+my $u_self   = { 'children' => \(my $children = 0), clist => [] };
+sub usb_dset(&) {
+    my($contents) = @_;
+    my $parent = $u_self;
+    my $children = 0;
+    my $index = ++${$u_self->{'children'}};
+
+    my $ds = { 'type' => 'dset',
+		   'parent' => $parent,
+		   'data' => undef,
+		   'bytes' => undef,
+		   'raw' => undef,
+		   'children' => \$children,
+		   'index' => \$index,
+		   'offs' => undef,
+		   'clist' => [] };
+    $u_self = $ds;
+    push(@{$parent->{'clist'}}, $ds);
+    my @data = $contents->($ds, $parent);
+    $ds->{'data'} = \@data;
+    $ds->{'bytes'} = datalen(@data);
+    $u_self = $parent;
+    return $ds;
+}
+sub usb_totallen(;$) {
+    my($r) = @_;
+    $r = $u_self unless(defined($r));
+
+    return word(\$r->{'bytes'});
+}
+sub usb_index(;$) {
+    my($r) = @_;
+    $r = $u_self unless(defined($r));
+
+    return byte($r->{'index'});
+}
+sub usb_peers(;$) {
+    my($r) = @_;
+    $r = $u_self unless(defined($r));
+
+    return byte($r->{'parent'}{'children'});
+}
+sub usb_children(;$) {
+    my($r) = @_;
+    $r = $u_self unless(defined($r));
+
+    return byte($r->{'children'});
+}
+
+# USB descriptor
+sub usb_desc($@) {
+    my($typestr, @data) = @_;
+
+    my($type,$subtype) = split(/\./, $typestr, 2);
+
+    my $tn;
+    my $sn;
+    my $hdr;
+    my $dlen = datalen(@data);
+
+    $tn = toint($type);
+    $tn = $DT{lc($type)} unless (defined($tn));
+    die "$0: unknown descriptor type: $typestr\n" unless (defined($tn));
+
+    if (defined($subtype)) {
+	$sn = toint($subtype);
+	$sn = $DS{$tn}->{$subtype} unless (defined($sn));
+	die "$0: unknown descriptor type: $typestr\n" unless (defined($sn));
+
+	$dlen += 3;
+	$hdr = pack("CCC", $dlen, $type, $subtype);
+    } else {
+	$dlen += 2;
+	$hdr = pack("CC", $dlen, $type);
+    }
+	return { 'type' => 'descriptor',
+		     'data' => [$hdr, @data],
+		     'bytes' => $dlen };
+}
+
+# Device top level
+my $device_dset;
+sub usb_device(&) {
+    my($contents) = @_;
+    $device_dset = usb_dset(\&$contents);
+}
+
+my @langlist;
+my %lang;
+my $stringdata;
+my %stringoffs;		  # Pointer into stringdata
+my %strdesci = ('' => 0); # String descriptor index (all strings identical)
+my @strdescs = ('');	  # Descriptor 0 means no string
+my $nstrdesc = 1;
+
+# Register a string into the string table and return a descriptor index byte.
+# Input should be a hash.
+sub usb_string(%) {
+    my(%strh) = @_;
+
+    my $descval = '';
+    my @txts = (undef) x scalar(@langlist);
+
+    my $found = $strh{''} ne ''; # Default string
+
+    foreach my $l (keys(%strh)) {
+	my $co = langid($l);
+	next unless (defined($co));
+	$txts[$lang{$co}] = $strh{$l};
+	$found += $strh{$l} ne '';
+    }
+
+    return pack("C", 0) unless ($found);
+
+    for (my $i = 0; $i < scalar(@langlist); $i++) {
+	my $co  = $langlist[$i];
+	my $txt = $txts[$i];
+	$txt = $strh{''} unless defined($txt);
+
+	my $utf16str = $utf16le->encode($txt, Encode::FB_WARN);
+	unless (defined($stringoffs{$utf16str})) {
+	    $stringoffs{$utf16str} = length($stringdata);
+	    $stringdata .= pack("CC", length($utf16str)+2, $DT{string});
+	    $stringdata .= $utf16str;
+	}
+
+	$descval .= pack("vv", $co, $stringoffs{$utf16str});
+    }
+
+    my $descindex = $strdesci{$descval};
+    unless (defined($descindex)) {
+	$descindex = $strdesci{$descval} = scalar @strdescs;
+	push(@strdescs, $descval);
+    }
+
+    return pack("C", $descindex);
+}
+
+sub usb_languages(@) {
+    my @langs = @_;
+
+    %lang = ();
+    @langlist = ();
+    foreach my $l (@langs) {
+	my $co = langid($l);
+	if (defined($co) && !defined($lang{$co})) {
+	    $lang{$co} = scalar(@langlist);
+	    push(@langlist, $co);
+	}
+    }
+
+    if (!scalar(@langlist)) {
+	$stringdata = '';
+    } else {
+	$stringdata = pack("CCv*", scalar(@langlist)*2 + 2,
+			   $DT{string}, @langlist);
+    }
+}
+
+my $descriptor_data;
+my @descriptor_ptrs;
+sub generate_data()
+{
+    my $data = '';
+
+    $data = makedata($device_dset);
+    my @ptrs = {'type' => $DT{device}, 'offs' => 0,
+		    'len' => unpack("C", substr($data, 0, 1))};
+
+    foreach my $dc (@{$device_dset->{'clist'}}) {
+	push(@ptrs, {'type' => unpack("C", substr($dc->{'raw'},
+						  $dc->{'offs'}+1, 1)),
+			 'dindex' => $dc->{'index'},
+			 'offs' => $dc->{'offs'},
+			 'len' => $dc->{'bytes'}});
+    }
+
+    my $string_offs = length($data);
+
+    push(@ptrs, {'type' => $DT{string}, 'dindex' => 0,
+		     'offs' => $string_offs,
+		     'len' => unpack("C", substr($stringdata, 0, 1))});
+
+    for (my $i = 1; $i < scalar(@strdescs); $i++) {
+	my @sds = unpack("v*", $strdescs[$i]);
+	while (scalar @sds >= 2) {
+	    my $co = shift(@sds);
+	    my $offs = shift(@sds);
+
+	    push(@ptrs, {'type' => $DT{string}, 'dindex' => $i,
+			     'windex' => $co,
+			     'offs' => $string_offs + $offs,
+			     'len' =>
+			     unpack("C", substr($stringdata, $offs, 1))});
+	}
+    }
+    $data .= $stringdata;
+
+    $descriptor_data = $data;
+    @descriptor_ptrs = @ptrs;
+
+    return $descriptor_data;
+}
+my($mode, $infile, $outfile) = @ARGV;
+
+unless (defined(do File::Spec->rel2abs($infile))) {
+    die "$0: $infile: $!\n"
+}
+
+generate_data();
+
+exit ($err) if ($err);
+
+open(my $out, '>', $outfile)
+    or die "$0: $outfile: $!\n";
+print $out $descriptor_data;
+close($out);
+
+if ($err) {
+    remove($outfile);
+}
+exit($err);