1#!/usr/bin/perl 2use strict; 3use Getopt::Long; 4 5# Copyright (C) 2014 Mauro Carvalho Chehab 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, version 2 of the License. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# This small script parses USB dumps generated by several drivers, 17# decoding USB bits. 18# 19# To use it, do: 20# dmesg | ./parse_usb.pl 21# 22# Also, there are other utilities that produce similar outputs, and it 23# is not hard to parse some USB analyzers log into the expected format. 24# 25 26my $debug = 0; 27my $show_timestamp = 0; 28my $hide_ir; 29my $hide_fw; 30my $hide_rd; 31my $hide_wr; 32my $hide_rw; 33my $hide_i2c_rd; 34my $hide_i2c_wr; 35my $hide_i2c_rw; 36my $hide_errors; 37my $help; 38 39my $helpmsg = "Use $0 [--debug] [--help] [--show_timestamp] [--hide-ir] [--hide-fw] [--hide-rd] [--hide-wr] [--hide-rw] [--hide-i2c-rd] [--hide-i2c-wr] [--hide-i2c-rw] [--hide-errors]\n"; 40my $argerr = "Invalid arguments.\n$helpmsg"; 41 42GetOptions( 43 'show_timestamp' => \$show_timestamp, 44 'hide_ir|hide-ir|hide-rc|hide_rc' => \$hide_ir, 45 'hide_fw|hide-fw' => \$hide_fw, 46 'hide_rd|hide-rd' => \$hide_rd, 47 'hide_wr|hide-wr' => \$hide_wr, 48 'hide_wr|hide-rw' => \$hide_rw, 49 'hide_i2c_rd|hide-i2c-rd' => \$hide_i2c_rd, 50 'hide_i2c_wr|hide-i2c-wr' => \$hide_i2c_wr, 51 'hide_i2c_rw|hide-i2c-rw' => \$hide_i2c_rw, 52 'hide_errors|hide-errors' => \$hide_errors, 53 'debug|v|d' => \$debug, 54 'h|help' => \$help, 55) or die $argerr; 56 57if ($help) { 58 print $helpmsg; 59 exit; 60} 61 62if ($hide_rw) { 63 $hide_rd = 1; 64 $hide_wr = 1; 65} 66 67if ($hide_i2c_rw) { 68 $hide_i2c_rd = 1; 69 $hide_i2c_wr = 1; 70} 71 72my $ctrl_ep = 0x02; 73my $resp_ep = 0x81; 74 75my %cmd_map = ( 76 0x00 => "CMD_MEM_RD", 77 0x01 => "CMD_MEM_WR", 78 0x02 => "CMD_I2C_RD", 79 0x03 => "CMD_I2C_WR", 80 0x04 => "CMD_EEPROM_READ", 81 0x05 => "CMD_EEPROM_WRITE", 82 0x18 => "CMD_IR_GET", 83 0x21 => "CMD_FW_DL", 84 0x22 => "CMD_FW_QUERYINFO", 85 0x23 => "CMD_FW_BOOT", 86 0x24 => "CMD_FW_DL_BEGIN", 87 0x25 => "CMD_FW_DL_END", 88 0x29 => "CMD_FW_SCATTER_WR", 89 0x2a => "CMD_GENERIC_I2C_RD", 90 0x2b => "CMD_GENERIC_I2C_WR", 91); 92 93my @stack; 94 95sub print_send_recv($$$$$$) 96{ 97 my ( $timestamp, $ep, $len, $seq, $status, $payload ) = @_; 98 99 my $data = pop @stack; 100 if (!$data) { 101 return if ($hide_errors); 102 $payload = ", recv_bytes = $payload" if ($payload && !($payload =~ /ERROR/)); 103 printf "Missing control cmd:\n"; 104 printf("\t%sRECV: len=%d, seq=%d, status=%d%s\n", 105 $timestamp, $len, $seq, $status, $payload); 106 return; 107 } 108 109 my ( $ctrl_ts, $ctrl_ep, $ctrl_len, $ctrl_seq, $ctrl_mbox, $ctrl_cmd, @ctrl_bytes ) = @$data; 110 111 if ($len && !$status && $ctrl_seq != $seq) { 112 return if ($hide_errors); 113 $payload = ", recv_bytes = $payload" if ($payload && !($payload =~ /ERROR/)); 114 printf "Wrong sequence number:\n"; 115 printf("\t%sSEND: len=%d, seq %d, mbox=0x%02x, cmd=%s%s", 116 $ctrl_ts, $ctrl_len, $ctrl_seq, $ctrl_mbox, $ctrl_cmd, $payload); 117 $timestamp = "($timestamp)" if ($timestamp); 118 printf(" RECV: len=%d, seq=%d, status=%d%s\n", 119 $len, $seq, $status, $timestamp); 120 print "\n"; 121 return; 122 } 123 124 $payload .= " - ERROR: af9035 status = $status" if ($status); 125 126 if (scalar(@ctrl_bytes) >= 3 && ($ctrl_cmd =~ /CMD_GENERIC_I2C_(RD|WR)/)) { 127 my @old = @ctrl_bytes; 128 my $len = shift @ctrl_bytes; 129 my $bus = shift @ctrl_bytes; 130 my $addr = (shift @ctrl_bytes) >> 1; 131 132 if (!scalar(@ctrl_bytes) && ($ctrl_cmd eq "CMD_GENERIC_I2C_RD")) { 133 my @b = split(/ /, $payload); 134 my $comment = "\t/* read: $payload */"; 135 136 printf "i2c_master_recv(bus%d, 0x%02x >> 1, &buf, %d);%s\n", $bus, $addr, scalar(@b), $comment if (!$hide_i2c_rd); 137 return; 138 } elsif ($ctrl_cmd eq "CMD_GENERIC_I2C_WR") { 139 my $comment = "\t/* $payload */" if ($payload =~ /ERROR/); 140 141 my $ctrl_pay; 142 for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { 143 if ($i == 0) { 144 $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; 145 } else { 146 $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; 147 } 148 } 149 150 printf "i2c_master_send(bus%d, 0x%02x >> 1, { %s }, %d);%s\n", $bus, $addr, $ctrl_pay, scalar(@ctrl_bytes), $comment if (!$hide_i2c_wr); 151 return; 152 } 153 @ctrl_bytes = @old; 154 } 155 156 if (scalar(@ctrl_bytes) >= 3 && ($ctrl_cmd =~ /CMD_I2C_(RD|WR)/)) { 157 my @old = @ctrl_bytes; 158 my $len = shift @ctrl_bytes; 159 my $addr = (shift @ctrl_bytes) >> 1; 160 my $rlen = shift @ctrl_bytes; 161 my $msb_raddr = shift @ctrl_bytes; 162 my $lsb_raddr = shift @ctrl_bytes; 163 164 if ($rlen == 2) { 165 unshift(@ctrl_bytes, $lsb_raddr); 166 unshift(@ctrl_bytes, $msb_raddr); 167 } elsif ($rlen == 1) { 168 unshift(@ctrl_bytes, $lsb_raddr); 169 } 170 171 if (!scalar(@ctrl_bytes) && ($ctrl_cmd eq "CMD_I2C_RD")) { 172 my @b = split(/ /, $payload); 173 my $comment = "\t/* read: $payload */"; 174 175 printf "i2c_master_recv(client, 0x%02x >> 1, &buf, %d);%s\n", $addr, scalar(@b), $comment if (!$hide_i2c_rd); 176 return; 177 } elsif ($ctrl_cmd eq "CMD_I2C_WR") { 178 my $comment = "\t/* $payload */" if ($payload =~ /ERROR/); 179 180 my $ctrl_pay; 181 for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { 182 if ($i == 0) { 183 $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; 184 } else { 185 $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; 186 } 187 } 188 189 printf "i2c_master_send(client, 0x%02x >> 1, { %s }, %d);%s\n", $addr, $ctrl_pay, scalar(@ctrl_bytes), $comment if (!$hide_i2c_wr); 190 return; 191 } 192 @ctrl_bytes = @old; 193 } 194 195 if (scalar(@ctrl_bytes) >= 6 && ($ctrl_cmd eq "CMD_MEM_WR" || $ctrl_cmd eq "CMD_MEM_RD")) { 196 my $wlen; 197 198 $wlen = shift @ctrl_bytes; 199 shift @ctrl_bytes; 200 shift @ctrl_bytes; 201 shift @ctrl_bytes; 202 203 my $reg = $ctrl_mbox << 16; 204 $reg |= (shift @ctrl_bytes) << 8; 205 $reg |= (shift @ctrl_bytes); 206 207 my $ctrl_pay; 208 for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { 209 if ($i == 0) { 210 $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; 211 } else { 212 $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; 213 } 214 } 215 216 if ($ctrl_cmd eq "CMD_MEM_WR") { 217 return if ($hide_wr); 218 my $comment = "\t/* $payload */" if ($payload =~ /ERROR/); 219 220 if (scalar(@ctrl_bytes) > 1) { 221 printf "ret = af9035_wr_regs(d, 0x%04x, %d, { $ctrl_pay });$comment\n", $reg, scalar(@ctrl_bytes); 222 } else { 223 printf "ret = af9035_wr_reg(d, 0x%04x, $ctrl_pay);$comment\n", $reg; 224 } 225 return; 226 } else { 227 return if ($hide_rd); 228 my $comment = "\t/* read: $payload */"; 229 if (scalar(@ctrl_bytes) > 0) { 230 printf "ret = af9035_rd_regs(d, 0x%04x, %d, { $ctrl_pay }, $len, rbuf);$comment\n", $reg, scalar(@ctrl_bytes); 231 } else { 232 printf "ret = af9035_rd_reg(d, 0x%04x, &val);$comment\n", $reg; 233 } 234 return; 235 } 236 } 237 238 if ($ctrl_cmd =~ /CMD_FW_(QUERYINFO|DL_BEGIN|DL_END|BOOT)/) { 239 my $comment = "\t/* read: $payload */" if ($payload); 240 printf "struct usb_req req = { $ctrl_cmd, $ctrl_mbox, $len, wbuf, sizeof(rbuf), rbuf }; ret = af9035_ctrl_msg(d, &req);$comment\n" if (!$hide_fw); 241 return; 242 } 243 244 if ($ctrl_cmd eq "CMD_IR_GET") { 245 my $comment = "\t/* read: $payload */" if ($payload); 246 printf "struct usb_req req = { $ctrl_cmd, $ctrl_mbox, $len, wbuf, sizeof(rbuf), rbuf }; ret = af9035_ctrl_msg(d, &req);$comment\n" if (!$hide_ir); 247 return; 248 } 249 250 my $ctrl_pay; 251 for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { 252 if ($i == 0) { 253 $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; 254 } else { 255 $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; 256 } 257 } 258 259 if ($ctrl_cmd eq "CMD_FW_DL") { 260 printf "af9015_wr_fw_block(%d, { $ctrl_pay };\n", scalar(@ctrl_bytes) if (!$hide_fw); 261 return; 262 } 263 264 265 $payload = ", recv_bytes = $payload" if ($payload && !($payload =~ /^ERROR/)); 266 $ctrl_pay = ", bytes = $ctrl_pay" if ($ctrl_pay); 267 268 printf("%sSEND: len=%d, seq %d, mbox=0x%02x, cmd=%s%s%s", 269 $ctrl_ts, $ctrl_len, $ctrl_seq, $ctrl_mbox, $ctrl_cmd, $ctrl_pay, $payload); 270 if ($ctrl_cmd ne "CMD_FW_DL") { 271 $timestamp = "($timestamp)" if ($timestamp); 272 printf(" RECV: len=%d, seq=%d, status=%d%s", 273 $len, $seq, $status, $timestamp) if ($status); 274 } 275 print "\n"; 276} 277 278while (<>) { 279 if (m/(\d+)\s+ms\s+(\d+)\s+ms\s+\((\d+)\s+us\s+EP\=([\da-fA-F]+).*[\<\>]+\s*(.*)/) { 280 my $timestamp = sprintf "%09u ms %6u ms %7u us ", $1, $2, $3; 281 my $ep = hex($4); 282 my $payload = $5; 283 284 printf("// %sEP=0x%02x: %s\n", $timestamp, $ep, $payload) if ($debug); 285 286 next if (!$payload); 287 288 $timestamp = "" if (!$show_timestamp); 289 290 next if (!($ep == $ctrl_ep || $ep == $resp_ep)); 291 292 my @bytes = split(/ /, $payload); 293 for (my $i = 0; $i < scalar(@bytes); $i++) { 294 $bytes[$i] = hex($bytes[$i]); 295 } 296 297 my $len = shift @bytes; 298 my ($mbox, $cmd, $seq, $status); 299 300 # Discount checksum and header length 301 if ($ep == $ctrl_ep) { 302 $mbox = shift @bytes; # Actually, part of CMD 303 $cmd = shift @bytes; 304 $seq = shift @bytes; 305 306 if (defined($cmd_map{$cmd})) { 307 $cmd = $cmd_map{$cmd}; 308 } else { 309 $cmd = sprintf "unknown 0x%02x", $cmd; 310 } 311 312 $len -= 4 + 1; 313 } else { 314 $seq = shift @bytes; 315 $status = shift @bytes; 316 317 $len -= 3 + 1; 318 } 319 my $checksum = pop @bytes; 320 $checksum |= (pop @bytes) << 8; 321 322 if ($ep == $ctrl_ep) { 323 my @data = ( $timestamp, $ep, $len, $seq, $mbox, $cmd, @bytes ); 324 push @stack, \@data; 325 326 if ($cmd eq "CMD_FW_DL") { 327 print_send_recv($timestamp, $ep, 0, 0, 0, ""); 328 } 329 330 next; 331 } 332 333 my $pay; 334 # Print everything, except the checksum 335 for (my $i = 0; $i < scalar(@bytes); $i++) { 336 if (!$i) { 337 $pay .= sprintf "0x%02x", $bytes[$i]; 338 } else { 339 $pay .= sprintf ", 0x%02x", $bytes[$i]; 340 } 341 } 342 343 print_send_recv($timestamp, $ep, $len, $seq, $status, $pay); 344 } 345} 346