Jelajahi Sumber

usbdescgen: handle serial number, produce Verilog code with index

Handle special strings (currently only the serial number) which will
need to be patched in or set at runtime.

Generate a Verilog module to get the index and length of various
descriptors in the ROM.

Fix the generation of descriptor pointers.
H. Peter Anvin 3 tahun lalu
induk
melakukan
4b594cfa8b
2 mengubah file dengan 270 tambahan dan 43 penghapusan
  1. 2 2
      fpga/usb/usb_desc.conf
  2. 268 41
      tools/usbdescgen.pl

+ 2 - 2
fpga/usb/usb_desc.conf

@@ -9,9 +9,9 @@ my $vendor_id    = 0x1d50;
 my $device_id    = 0x6149;
 my $version_id   = 0x0100;
 
+my $serial       = usb_serial('?' x 16);
 my $manufacturer = usb_string('' => 'Peter & Per');
 my $product      = usb_string('' => 'MAX80');
-my $serial       = usb_string('' => ('?' x 16));
 
 my $mgmt_if, $data_if;
 
@@ -27,7 +27,7 @@ usb_device {
 
 	usb_dset {
 	    usb_desc('configuration',
-		     usb_totallen,
+		     usb_totallen,	 # Total length for this dset
 		     usb_children,	 # Number of interfaces
 		     usb_index,		 # Configuration index
 		     usb_string(),	 # Text description (empty)

+ 268 - 41
tools/usbdescgen.pl

@@ -10,6 +10,11 @@ use Encode;
 
 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;
 # some USB documents specify in hex and others in decimal...
 our %DT = (
@@ -215,12 +220,21 @@ sub ep_o($) {
     return byte($n|0x00);
 }
 
+
+#
+# General handy stuff...
+#
+
+# Decode an integer in C notation
 sub toint($) {
     my($i) = @_;
     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
 sub usb_class($;$$) {
@@ -246,7 +260,7 @@ sub usb_class($;$$) {
 		$cc = 0;
 	    }
 
-	    $cd .= byte($cc);
+	    $cd .= pack('C', $cc);
 	    $lvl = $lvl->{$cc};
 	}
     }
@@ -280,18 +294,18 @@ sub makedata(@) {
     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;
+	    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') {
 	    $o .= makedata(@$b);
 	} elsif ($t eq 'SCALAR') {
@@ -377,14 +391,15 @@ sub usb_desc($@) {
 	die "$0: unknown descriptor type: $typestr\n" unless (defined($sn));
 
 	$dlen += 3;
-	$hdr = pack("CCC", $dlen, $type, $subtype);
+	$hdr = pack('CCC', $dlen, $tn, $sn);
     } else {
 	$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
@@ -397,10 +412,20 @@ sub usb_device(&) {
 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;
+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.
 # Input should be a hash.
@@ -419,7 +444,7 @@ sub usb_string(%) {
 	$found += $strh{$l} ne '';
     }
 
-    return pack("C", 0) unless ($found);
+    return pack('C', 0) unless ($found);
 
     for (my $i = 0; $i < scalar(@langlist); $i++) {
 	my $co  = $langlist[$i];
@@ -429,22 +454,65 @@ sub usb_string(%) {
 	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 .= pack('CC', length($utf16str)+2, $DT{string});
 	    $stringdata .= $utf16str;
 	}
 
-	$descval .= pack("vv", $co, $stringoffs{$utf16str});
+	$descval .= pack('vv', $co, $stringoffs{$utf16str});
     }
 
     my $descindex = $strdesci{$descval};
     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);
     }
 
-    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(@) {
     my @langs = @_;
 
@@ -468,27 +536,44 @@ sub usb_languages(@) {
 
 my $descriptor_data;
 my @descriptor_ptrs;
+
 sub generate_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);
 
+    # 'Regular' string descriptors
     push(@ptrs, {'type' => $DT{string}, 'dindex' => 0,
 		     '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++) {
 	my @sds = unpack("v*", $strdescs[$i]);
@@ -497,10 +582,10 @@ sub generate_data()
 	    my $offs = shift(@sds);
 
 	    push(@ptrs, {'type' => $DT{string}, 'dindex' => $i,
-			     'windex' => $co,
+			     'windex' => $co, 'wimask' => 0x03ff, # FIX THIS
 			     'offs' => $string_offs + $offs,
 			     'len' =>
-			     unpack("C", substr($stringdata, $offs, 1))});
+			     unpack('C', substr($stringdata, $offs, 1))});
 	}
     }
     $data .= $stringdata;
@@ -510,7 +595,142 @@ sub generate_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))) {
     die "$0: $infile: $!\n"
@@ -522,7 +742,14 @@ exit ($err) if ($err);
 
 open(my $out, '>', $outfile)
     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);
 
 if ($err) {