|
@@ -10,6 +10,11 @@ use Encode;
|
|
|
|
|
|
require 'langid.ph';
|
|
require 'langid.ph';
|
|
|
|
|
|
|
|
+# Some forward declarations
|
|
|
|
+my($mode, $infile, $outfile) = @ARGV;
|
|
|
|
+my $module_name;
|
|
|
|
+my $err = 0;
|
|
|
|
+
|
|
# Descriptor types. This follows the document in which they were specified;
|
|
# Descriptor types. This follows the document in which they were specified;
|
|
# some USB documents specify in hex and others in decimal...
|
|
# some USB documents specify in hex and others in decimal...
|
|
our %DT = (
|
|
our %DT = (
|
|
@@ -215,12 +220,21 @@ sub ep_o($) {
|
|
return byte($n|0x00);
|
|
return byte($n|0x00);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
|
|
+#
|
|
|
|
+# General handy stuff...
|
|
|
|
+#
|
|
|
|
+
|
|
|
|
+# Decode an integer in C notation
|
|
sub toint($) {
|
|
sub toint($) {
|
|
my($i) = @_;
|
|
my($i) = @_;
|
|
return ($i =~ /^0/) ? oct $i : ($i =~ /^[1-9]/) ? $i+0 : undef;
|
|
return ($i =~ /^0/) ? oct $i : ($i =~ /^[1-9]/) ? $i+0 : undef;
|
|
}
|
|
}
|
|
|
|
|
|
-my $err = 0;
|
|
|
|
|
|
+# For sorting
|
|
|
|
+sub numeric {
|
|
|
|
+ return $a <=> $b;
|
|
|
|
+}
|
|
|
|
|
|
# Generate class code triplets
|
|
# Generate class code triplets
|
|
sub usb_class($;$$) {
|
|
sub usb_class($;$$) {
|
|
@@ -246,7 +260,7 @@ sub usb_class($;$$) {
|
|
$cc = 0;
|
|
$cc = 0;
|
|
}
|
|
}
|
|
|
|
|
|
- $cd .= byte($cc);
|
|
|
|
|
|
+ $cd .= pack('C', $cc);
|
|
$lvl = $lvl->{$cc};
|
|
$lvl = $lvl->{$cc};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -280,18 +294,18 @@ sub makedata(@) {
|
|
foreach my $b (@_) {
|
|
foreach my $b (@_) {
|
|
my $t = ref $b;
|
|
my $t = ref $b;
|
|
if ($t eq 'HASH') {
|
|
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;
|
|
|
|
|
|
+ unless (defined($b->{'raw'})) {
|
|
|
|
+ if (defined($b->{'num'})) {
|
|
|
|
+ $b->{'raw'} = pack($packfmt{$b->{'bytes'}}, ${$b->{'num'}});
|
|
|
|
+ } elsif (defined($b->{'data'})) {
|
|
|
|
+ $b->{'raw'} = makedata($b->{'data'});
|
|
|
|
+ } else {
|
|
|
|
+ die;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ $b->{'offs'} = length($o);
|
|
|
|
+ $b->{'bytes'} = length($b->{'raw'});
|
|
|
|
+ $o .= $b->{'raw'};
|
|
} elsif ($t eq 'ARRAY') {
|
|
} elsif ($t eq 'ARRAY') {
|
|
$o .= makedata(@$b);
|
|
$o .= makedata(@$b);
|
|
} elsif ($t eq 'SCALAR') {
|
|
} elsif ($t eq 'SCALAR') {
|
|
@@ -377,14 +391,15 @@ sub usb_desc($@) {
|
|
die "$0: unknown descriptor type: $typestr\n" unless (defined($sn));
|
|
die "$0: unknown descriptor type: $typestr\n" unless (defined($sn));
|
|
|
|
|
|
$dlen += 3;
|
|
$dlen += 3;
|
|
- $hdr = pack("CCC", $dlen, $type, $subtype);
|
|
|
|
|
|
+ $hdr = pack('CCC', $dlen, $tn, $sn);
|
|
} else {
|
|
} else {
|
|
$dlen += 2;
|
|
$dlen += 2;
|
|
- $hdr = pack("CC", $dlen, $type);
|
|
|
|
|
|
+ $hdr = pack('CC', $dlen, $tn);
|
|
}
|
|
}
|
|
- return { 'type' => 'descriptor',
|
|
|
|
- 'data' => [$hdr, @data],
|
|
|
|
- 'bytes' => $dlen };
|
|
|
|
|
|
+
|
|
|
|
+ return { 'type' => 'descriptor',
|
|
|
|
+ 'data' => [$hdr, @data],
|
|
|
|
+ 'bytes' => $dlen };
|
|
}
|
|
}
|
|
|
|
|
|
# Device top level
|
|
# Device top level
|
|
@@ -397,10 +412,20 @@ sub usb_device(&) {
|
|
my @langlist;
|
|
my @langlist;
|
|
my %lang;
|
|
my %lang;
|
|
my $stringdata;
|
|
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;
|
|
|
|
|
|
+my %stringoffs; # Pointer into stringdata
|
|
|
|
+
|
|
|
|
+# Reserved string descriptor numbers
|
|
|
|
+my $strdesc_empty = 0; # 0 = reserved for all null strings
|
|
|
|
+my $strdesc_serial = 1; # 1 = reserved for serial number (see below)
|
|
|
|
+my $strdesc_msft = 0xee;
|
|
|
|
+
|
|
|
|
+my %special_strings = ($strdesc_serial => undef,
|
|
|
|
+ $strdesc_msft => undef);
|
|
|
|
+
|
|
|
|
+# The index of string descriptors are sets of descriptors which
|
|
|
|
+# match for ALL languages so they can be given the same index.
|
|
|
|
+my %strdesci = ('' => $strdesc_empty);
|
|
|
|
+my @strdescs = ('');
|
|
|
|
|
|
# Register a string into the string table and return a descriptor index byte.
|
|
# Register a string into the string table and return a descriptor index byte.
|
|
# Input should be a hash.
|
|
# Input should be a hash.
|
|
@@ -419,7 +444,7 @@ sub usb_string(%) {
|
|
$found += $strh{$l} ne '';
|
|
$found += $strh{$l} ne '';
|
|
}
|
|
}
|
|
|
|
|
|
- return pack("C", 0) unless ($found);
|
|
|
|
|
|
+ return pack('C', 0) unless ($found);
|
|
|
|
|
|
for (my $i = 0; $i < scalar(@langlist); $i++) {
|
|
for (my $i = 0; $i < scalar(@langlist); $i++) {
|
|
my $co = $langlist[$i];
|
|
my $co = $langlist[$i];
|
|
@@ -429,22 +454,65 @@ sub usb_string(%) {
|
|
my $utf16str = $utf16le->encode($txt, Encode::FB_WARN);
|
|
my $utf16str = $utf16le->encode($txt, Encode::FB_WARN);
|
|
unless (defined($stringoffs{$utf16str})) {
|
|
unless (defined($stringoffs{$utf16str})) {
|
|
$stringoffs{$utf16str} = length($stringdata);
|
|
$stringoffs{$utf16str} = length($stringdata);
|
|
- $stringdata .= pack("CC", length($utf16str)+2, $DT{string});
|
|
|
|
|
|
+ $stringdata .= pack('CC', length($utf16str)+2, $DT{string});
|
|
$stringdata .= $utf16str;
|
|
$stringdata .= $utf16str;
|
|
}
|
|
}
|
|
|
|
|
|
- $descval .= pack("vv", $co, $stringoffs{$utf16str});
|
|
|
|
|
|
+ $descval .= pack('vv', $co, $stringoffs{$utf16str});
|
|
}
|
|
}
|
|
|
|
|
|
my $descindex = $strdesci{$descval};
|
|
my $descindex = $strdesci{$descval};
|
|
unless (defined($descindex)) {
|
|
unless (defined($descindex)) {
|
|
- $descindex = $strdesci{$descval} = scalar @strdescs;
|
|
|
|
|
|
+ $descindex = scalar(@strdescs) - 1;
|
|
|
|
+ while (exists($special_strings{++$descindex})) {
|
|
|
|
+ push(@strdescs, undef); # Skip reserved index
|
|
|
|
+ }
|
|
|
|
+ $strdesci{$descval} = $descindex;
|
|
push(@strdescs, $descval);
|
|
push(@strdescs, $descval);
|
|
}
|
|
}
|
|
|
|
|
|
- return pack("C", $descindex);
|
|
|
|
|
|
+ return pack('C', $descindex);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#
|
|
|
|
+# Special string descriptors. Currently supports the serial number,
|
|
|
|
+# but could also be used for the M$ special 0xEE string descriptor
|
|
|
|
+# with a bit more infrastructure. Noteworthy is that the serial number
|
|
|
|
+# descriptor is quite likely to need to be patched in the field or
|
|
|
|
+# at runtime. Thus, these special string descriptors come at the
|
|
|
|
+# very beginning of the data, in index order.
|
|
|
|
+#
|
|
|
|
+# Language codes are not supported for special strings.
|
|
|
|
+#
|
|
|
|
+# Usage: usb_special_string(index,'default',length)
|
|
|
|
+#
|
|
|
|
+sub usb_special_string($@) {
|
|
|
|
+ my($ix,$dft,$len) = @_;
|
|
|
|
+
|
|
|
|
+ if (defined($dft) || $len || !defined($special_strings{$ix})) {
|
|
|
|
+ $dft = $utf16le->encode($dft, Encode::FB_WARN);
|
|
|
|
+ my $dchar = length($dft) >> 1;
|
|
|
|
+ if (!$len) {
|
|
|
|
+ $len = $dchar;
|
|
|
|
+ } elsif ($len < $dchar) {
|
|
|
|
+ $dft = substr($dft, 0, $len << 1);
|
|
|
|
+ } elsif ($len > $dchar) {
|
|
|
|
+ # Pad with spaces
|
|
|
|
+ $dft .= $utf16le->encode(' ' x ($len - $dchar));
|
|
|
|
+ }
|
|
|
|
+ $special_strings{$ix} = pack('CC', ($len << 1)+2, $DT{string}).$dft;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return pack('C', $ix);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+sub usb_serial($;$) {
|
|
|
|
+ return usb_special_string($strdesc_serial, @_);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#
|
|
|
|
+# Register the possible languages
|
|
|
|
+#
|
|
sub usb_languages(@) {
|
|
sub usb_languages(@) {
|
|
my @langs = @_;
|
|
my @langs = @_;
|
|
|
|
|
|
@@ -468,27 +536,44 @@ sub usb_languages(@) {
|
|
|
|
|
|
my $descriptor_data;
|
|
my $descriptor_data;
|
|
my @descriptor_ptrs;
|
|
my @descriptor_ptrs;
|
|
|
|
+
|
|
sub generate_data()
|
|
sub generate_data()
|
|
{
|
|
{
|
|
my $data = '';
|
|
my $data = '';
|
|
|
|
+ my @ptrs = ();
|
|
|
|
+
|
|
|
|
+ # Special string descriptors first
|
|
|
|
+ foreach my $ssn (sort numeric keys(%special_strings)) {
|
|
|
|
+ my $ss = $special_strings{$ssn};
|
|
|
|
+
|
|
|
|
+ next unless defined($ss);
|
|
|
|
|
|
- $data = makedata($device_dset);
|
|
|
|
- my @ptrs = {'type' => $DT{device}, 'offs' => 0,
|
|
|
|
- 'len' => unpack("C", substr($data, 0, 1))};
|
|
|
|
|
|
+ push(@ptrs, {'type' => $DT{string}, 'dindex' => $ssn,
|
|
|
|
+ 'offs' => length($data),
|
|
|
|
+ 'len' => length($ss)});
|
|
|
|
+ $data .= $ss;
|
|
|
|
+ }
|
|
|
|
|
|
- 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'}});
|
|
|
|
|
|
+ # Device and configuration descriptors
|
|
|
|
+ my $dev_offs = length($data);
|
|
|
|
+ $data .= makedata($device_dset);
|
|
|
|
+
|
|
|
|
+ foreach my $dc (@{$device_dset->{data}}) {
|
|
|
|
+ my $offs = $dev_offs + $dc->{offs};
|
|
|
|
+ # Length and type of the *first* descriptor in a set
|
|
|
|
+ my($flen,$ftype) = unpack('CC', substr($data, $offs, 2));
|
|
|
|
+ my $dindex = $dc->{index};
|
|
|
|
+ $dindex = $$dindex while (ref $dindex eq 'SCALAR');
|
|
|
|
+ push(@ptrs, {'type' => $ftype, 'dindex' => $dindex,
|
|
|
|
+ 'offs' => $offs, 'len' => $dc->{bytes}});
|
|
}
|
|
}
|
|
|
|
|
|
my $string_offs = length($data);
|
|
my $string_offs = length($data);
|
|
|
|
|
|
|
|
+ # 'Regular' string descriptors
|
|
push(@ptrs, {'type' => $DT{string}, 'dindex' => 0,
|
|
push(@ptrs, {'type' => $DT{string}, 'dindex' => 0,
|
|
'offs' => $string_offs,
|
|
'offs' => $string_offs,
|
|
- 'len' => unpack("C", substr($stringdata, 0, 1))});
|
|
|
|
|
|
+ 'len' => unpack('C', substr($stringdata, 0, 1))});
|
|
|
|
|
|
for (my $i = 1; $i < scalar(@strdescs); $i++) {
|
|
for (my $i = 1; $i < scalar(@strdescs); $i++) {
|
|
my @sds = unpack("v*", $strdescs[$i]);
|
|
my @sds = unpack("v*", $strdescs[$i]);
|
|
@@ -497,10 +582,10 @@ sub generate_data()
|
|
my $offs = shift(@sds);
|
|
my $offs = shift(@sds);
|
|
|
|
|
|
push(@ptrs, {'type' => $DT{string}, 'dindex' => $i,
|
|
push(@ptrs, {'type' => $DT{string}, 'dindex' => $i,
|
|
- 'windex' => $co,
|
|
|
|
|
|
+ 'windex' => $co, 'wimask' => 0x03ff, # FIX THIS
|
|
'offs' => $string_offs + $offs,
|
|
'offs' => $string_offs + $offs,
|
|
'len' =>
|
|
'len' =>
|
|
- unpack("C", substr($stringdata, $offs, 1))});
|
|
|
|
|
|
+ unpack('C', substr($stringdata, $offs, 1))});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$data .= $stringdata;
|
|
$data .= $stringdata;
|
|
@@ -510,7 +595,142 @@ sub generate_data()
|
|
|
|
|
|
return $descriptor_data;
|
|
return $descriptor_data;
|
|
}
|
|
}
|
|
-my($mode, $infile, $outfile) = @ARGV;
|
|
|
|
|
|
+
|
|
|
|
+# Output a number and a mask as a Verilog case with ? as needed
|
|
|
|
+sub v_case($$$) {
|
|
|
|
+ my($b,$v,$m) = @_;
|
|
|
|
+
|
|
|
|
+ my $o = sprintf("%d'b", $b);
|
|
|
|
+
|
|
|
|
+ for (my $i = $b-1; $i >= 0; $i--) {
|
|
|
|
+ my $ix = 1 << $i;
|
|
|
|
+ $o .= ($m & $ix) ? (($v & $ix) ? '1' : '0') : '?';
|
|
|
|
+ $o .= '_' if ($i > 0 && ($i & 7) == 0);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $o;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# Output a Verilog module
|
|
|
|
+sub output_verilog($) {
|
|
|
|
+ my($out) = @_;
|
|
|
|
+
|
|
|
|
+ my $bytes = length($descriptor_data);
|
|
|
|
+
|
|
|
|
+ my $abits = 1;
|
|
|
|
+ while ((1 << $abits) < $bytes) {
|
|
|
|
+ $abits++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ my $amax = $abits-1;
|
|
|
|
+ my $bmax = (1 << $abits)-1;
|
|
|
|
+
|
|
|
|
+ my $afmt = sprintf("%d'h%%0%dx", $abits, ($abits+3) >> 2);
|
|
|
|
+ my $abad = sprintf("%d'h", $abits) . ('x' x (($abits+3) >> 2));
|
|
|
|
+ my $bfmt = "8'h%02x";
|
|
|
|
+ my $bbad = "8'hxx";
|
|
|
|
+
|
|
|
|
+ print $out <<"EOF";
|
|
|
|
+/*
|
|
|
|
+ * Call it a ROM even through it can be optionally written to.
|
|
|
|
+ * Trust the tools to figure out if we don't need part of the whole thing.
|
|
|
|
+ */
|
|
|
|
+module ${module_name}_rom (
|
|
|
|
+ input clk,
|
|
|
|
+
|
|
|
|
+ input [$amax:0] usb_addr,
|
|
|
|
+ output [7:0] usb_rdata,
|
|
|
|
+
|
|
|
|
+ input [$amax:0] cpu_addr,
|
|
|
|
+ output [7:0] cpu_rdata,
|
|
|
|
+ input [7:0] cpu_wdata,
|
|
|
|
+ input cpu_wren
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+ reg [7:0] rom [0:$bmax];
|
|
|
|
+
|
|
|
|
+ initial begin
|
|
|
|
+EOF
|
|
|
|
+
|
|
|
|
+ my $addr = 0;
|
|
|
|
+ foreach my $b (unpack("C*", $descriptor_data)) {
|
|
|
|
+ printf $out "\t\trom[$afmt] = $bfmt;\n", $addr++, $b;
|
|
|
|
+ }
|
|
|
|
+ while ($addr < $bytes) {
|
|
|
|
+ printf $out "\t\trom[$afmt] = $bbad;\n", $addr++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ print $out <<"EOF";
|
|
|
|
+ end
|
|
|
|
+
|
|
|
|
+ always \@(posedge clk) begin
|
|
|
|
+ usb_rdata <= rom[usb_addr];
|
|
|
|
+ cpu_rdata <= rom[cpu_addr];
|
|
|
|
+ if (cpu_wren)
|
|
|
|
+ rom[cpu_addr] <= cpu_wdata;
|
|
|
|
+ end
|
|
|
|
+endmodule
|
|
|
|
+
|
|
|
|
+module ${module_name}_index (
|
|
|
|
+ input [7:0] dtype,
|
|
|
|
+ input [7:0] dindex,
|
|
|
|
+ input [15:0] windex,
|
|
|
|
+
|
|
|
|
+ output reg [$amax:0] addr,
|
|
|
|
+ output reg [$amax:0] len
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+ always \@(\*) priority casez ({windex,dindex,dtype})
|
|
|
|
+EOF
|
|
|
|
+
|
|
|
|
+ my @cases;
|
|
|
|
+
|
|
|
|
+ foreach my $d ({ 'len' => 0 }, @descriptor_ptrs) {
|
|
|
|
+ my $id = 0;
|
|
|
|
+ my $mask = 0;
|
|
|
|
+
|
|
|
|
+ if (defined($d->{type})) {
|
|
|
|
+ $id |= $d->{type};
|
|
|
|
+ $mask |= 0xff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (defined($d->{dindex})) {
|
|
|
|
+ $id |= $d->{dindex} << 8;
|
|
|
|
+ $mask |= 0xff << 8;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (defined($d->{windex})) {
|
|
|
|
+ $id |= $d->{windex} << 16;
|
|
|
|
+ $mask |= $d->{wimask} << 16;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ my $cs = "\t\t".v_case(32,$id,$mask).': {addr,len} = ';
|
|
|
|
+ if ($d->{len}) {
|
|
|
|
+ $cs .= sprintf("{$afmt,$afmt};\n", $d->{offs},$d->{len});
|
|
|
|
+ } else {
|
|
|
|
+ $cs .= sprintf("{$abad,$afmt};\n", $d->{len});
|
|
|
|
+ }
|
|
|
|
+ push(@cases, $cs);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ # This relies on ? sorting after digits
|
|
|
|
+ print $out sort @cases;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ print $out <<'EOF';
|
|
|
|
+ endcase
|
|
|
|
+endmodule
|
|
|
|
+EOF
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+if (scalar(@ARGV) < 3) {
|
|
|
|
+ die "Usage: $0 {bin|v} input output\n";
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+my @ofs = File::Spec->splitpath($outfile);
|
|
|
|
+$module_name = $ofs[2];
|
|
|
|
+$module_name =~ s/\..*$//;
|
|
|
|
+$module_name =~ s/\P{Alnum}+/_/g;
|
|
|
|
|
|
unless (defined(do File::Spec->rel2abs($infile))) {
|
|
unless (defined(do File::Spec->rel2abs($infile))) {
|
|
die "$0: $infile: $!\n"
|
|
die "$0: $infile: $!\n"
|
|
@@ -522,7 +742,14 @@ exit ($err) if ($err);
|
|
|
|
|
|
open(my $out, '>', $outfile)
|
|
open(my $out, '>', $outfile)
|
|
or die "$0: $outfile: $!\n";
|
|
or die "$0: $outfile: $!\n";
|
|
-print $out $descriptor_data;
|
|
|
|
|
|
+
|
|
|
|
+if ($mode eq 'bin') {
|
|
|
|
+ print $out $descriptor_data;
|
|
|
|
+} elsif ($mode eq 'v') {
|
|
|
|
+ output_verilog($out);
|
|
|
|
+} else {
|
|
|
|
+ die "$0: $mode: unknown operations mode\n";
|
|
|
|
+}
|
|
close($out);
|
|
close($out);
|
|
|
|
|
|
if ($err) {
|
|
if ($err) {
|