#!/usr/bin/perl use strict; use Getopt::Long; # Copyright (C) 2014 Mauro Carvalho Chehab # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # This small script parses USB dumps generated by several drivers, # decoding USB bits. # # To use it, do: # dmesg | ./parse_usb.pl # # Also, there are other utilities that produce similar outputs, and it # is not hard to parse some USB analyzers log into the expected format. # my $debug = 0; my $show_timestamp = 0; my $hide_ir; my $hide_fw; my $hide_rd; my $hide_wr; my $hide_rw; my $hide_i2c_rd; my $hide_i2c_wr; my $hide_i2c_rw; my $hide_errors; my $help; my $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"; my $argerr = "Invalid arguments.\n$helpmsg"; GetOptions( 'show_timestamp' => \$show_timestamp, 'hide_ir|hide-ir|hide-rc|hide_rc' => \$hide_ir, 'hide_fw|hide-fw' => \$hide_fw, 'hide_rd|hide-rd' => \$hide_rd, 'hide_wr|hide-wr' => \$hide_wr, 'hide_wr|hide-rw' => \$hide_rw, 'hide_i2c_rd|hide-i2c-rd' => \$hide_i2c_rd, 'hide_i2c_wr|hide-i2c-wr' => \$hide_i2c_wr, 'hide_i2c_rw|hide-i2c-rw' => \$hide_i2c_rw, 'hide_errors|hide-errors' => \$hide_errors, 'debug|v|d' => \$debug, 'h|help' => \$help, ) or die $argerr; if ($help) { print $helpmsg; exit; } if ($hide_rw) { $hide_rd = 1; $hide_wr = 1; } if ($hide_i2c_rw) { $hide_i2c_rd = 1; $hide_i2c_wr = 1; } my $ctrl_ep = 0x02; my $resp_ep = 0x81; my %cmd_map = ( 0x00 => "CMD_MEM_RD", 0x01 => "CMD_MEM_WR", 0x02 => "CMD_I2C_RD", 0x03 => "CMD_I2C_WR", 0x04 => "CMD_EEPROM_READ", 0x05 => "CMD_EEPROM_WRITE", 0x18 => "CMD_IR_GET", 0x21 => "CMD_FW_DL", 0x22 => "CMD_FW_QUERYINFO", 0x23 => "CMD_FW_BOOT", 0x24 => "CMD_FW_DL_BEGIN", 0x25 => "CMD_FW_DL_END", 0x29 => "CMD_FW_SCATTER_WR", 0x2a => "CMD_GENERIC_I2C_RD", 0x2b => "CMD_GENERIC_I2C_WR", ); my @stack; sub print_send_recv($$$$$$) { my ( $timestamp, $ep, $len, $seq, $status, $payload ) = @_; my $data = pop @stack; if (!$data) { return if ($hide_errors); $payload = ", recv_bytes = $payload" if ($payload && !($payload =~ /ERROR/)); printf "Missing control cmd:\n"; printf("\t%sRECV: len=%d, seq=%d, status=%d%s\n", $timestamp, $len, $seq, $status, $payload); return; } my ( $ctrl_ts, $ctrl_ep, $ctrl_len, $ctrl_seq, $ctrl_mbox, $ctrl_cmd, @ctrl_bytes ) = @$data; if ($len && !$status && $ctrl_seq != $seq) { return if ($hide_errors); $payload = ", recv_bytes = $payload" if ($payload && !($payload =~ /ERROR/)); printf "Wrong sequence number:\n"; printf("\t%sSEND: len=%d, seq %d, mbox=0x%02x, cmd=%s%s", $ctrl_ts, $ctrl_len, $ctrl_seq, $ctrl_mbox, $ctrl_cmd, $payload); $timestamp = "($timestamp)" if ($timestamp); printf(" RECV: len=%d, seq=%d, status=%d%s\n", $len, $seq, $status, $timestamp); print "\n"; return; } $payload .= " - ERROR: af9035 status = $status" if ($status); if (scalar(@ctrl_bytes) >= 3 && ($ctrl_cmd =~ /CMD_GENERIC_I2C_(RD|WR)/)) { my @old = @ctrl_bytes; my $len = shift @ctrl_bytes; my $bus = shift @ctrl_bytes; my $addr = (shift @ctrl_bytes) >> 1; if (!scalar(@ctrl_bytes) && ($ctrl_cmd eq "CMD_GENERIC_I2C_RD")) { my @b = split(/ /, $payload); my $comment = "\t/* read: $payload */"; printf "i2c_master_recv(bus%d, 0x%02x >> 1, &buf, %d);%s\n", $bus, $addr, scalar(@b), $comment if (!$hide_i2c_rd); return; } elsif ($ctrl_cmd eq "CMD_GENERIC_I2C_WR") { my $comment = "\t/* $payload */" if ($payload =~ /ERROR/); my $ctrl_pay; for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { if ($i == 0) { $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; } else { $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; } } 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); return; } @ctrl_bytes = @old; } if (scalar(@ctrl_bytes) >= 3 && ($ctrl_cmd =~ /CMD_I2C_(RD|WR)/)) { my @old = @ctrl_bytes; my $len = shift @ctrl_bytes; my $addr = (shift @ctrl_bytes) >> 1; my $rlen = shift @ctrl_bytes; my $msb_raddr = shift @ctrl_bytes; my $lsb_raddr = shift @ctrl_bytes; if ($rlen == 2) { unshift(@ctrl_bytes, $lsb_raddr); unshift(@ctrl_bytes, $msb_raddr); } elsif ($rlen == 1) { unshift(@ctrl_bytes, $lsb_raddr); } if (!scalar(@ctrl_bytes) && ($ctrl_cmd eq "CMD_I2C_RD")) { my @b = split(/ /, $payload); my $comment = "\t/* read: $payload */"; printf "i2c_master_recv(client, 0x%02x >> 1, &buf, %d);%s\n", $addr, scalar(@b), $comment if (!$hide_i2c_rd); return; } elsif ($ctrl_cmd eq "CMD_I2C_WR") { my $comment = "\t/* $payload */" if ($payload =~ /ERROR/); my $ctrl_pay; for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { if ($i == 0) { $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; } else { $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; } } printf "i2c_master_send(client, 0x%02x >> 1, { %s }, %d);%s\n", $addr, $ctrl_pay, scalar(@ctrl_bytes), $comment if (!$hide_i2c_wr); return; } @ctrl_bytes = @old; } if (scalar(@ctrl_bytes) >= 6 && ($ctrl_cmd eq "CMD_MEM_WR" || $ctrl_cmd eq "CMD_MEM_RD")) { my $wlen; $wlen = shift @ctrl_bytes; shift @ctrl_bytes; shift @ctrl_bytes; shift @ctrl_bytes; my $reg = $ctrl_mbox << 16; $reg |= (shift @ctrl_bytes) << 8; $reg |= (shift @ctrl_bytes); my $ctrl_pay; for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { if ($i == 0) { $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; } else { $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; } } if ($ctrl_cmd eq "CMD_MEM_WR") { return if ($hide_wr); my $comment = "\t/* $payload */" if ($payload =~ /ERROR/); if (scalar(@ctrl_bytes) > 1) { printf "ret = af9035_wr_regs(d, 0x%04x, %d, { $ctrl_pay });$comment\n", $reg, scalar(@ctrl_bytes); } else { printf "ret = af9035_wr_reg(d, 0x%04x, $ctrl_pay);$comment\n", $reg; } return; } else { return if ($hide_rd); my $comment = "\t/* read: $payload */"; if (scalar(@ctrl_bytes) > 0) { printf "ret = af9035_rd_regs(d, 0x%04x, %d, { $ctrl_pay }, $len, rbuf);$comment\n", $reg, scalar(@ctrl_bytes); } else { printf "ret = af9035_rd_reg(d, 0x%04x, &val);$comment\n", $reg; } return; } } if ($ctrl_cmd =~ /CMD_FW_(QUERYINFO|DL_BEGIN|DL_END|BOOT)/) { my $comment = "\t/* read: $payload */" if ($payload); 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); return; } if ($ctrl_cmd eq "CMD_IR_GET") { my $comment = "\t/* read: $payload */" if ($payload); 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); return; } my $ctrl_pay; for (my $i = 0; $i < scalar(@ctrl_bytes); $i++) { if ($i == 0) { $ctrl_pay .= sprintf "0x%02x", $ctrl_bytes[$i]; } else { $ctrl_pay .= sprintf ", 0x%02x", $ctrl_bytes[$i]; } } if ($ctrl_cmd eq "CMD_FW_DL") { printf "af9015_wr_fw_block(%d, { $ctrl_pay };\n", scalar(@ctrl_bytes) if (!$hide_fw); return; } $payload = ", recv_bytes = $payload" if ($payload && !($payload =~ /^ERROR/)); $ctrl_pay = ", bytes = $ctrl_pay" if ($ctrl_pay); printf("%sSEND: len=%d, seq %d, mbox=0x%02x, cmd=%s%s%s", $ctrl_ts, $ctrl_len, $ctrl_seq, $ctrl_mbox, $ctrl_cmd, $ctrl_pay, $payload); if ($ctrl_cmd ne "CMD_FW_DL") { $timestamp = "($timestamp)" if ($timestamp); printf(" RECV: len=%d, seq=%d, status=%d%s", $len, $seq, $status, $timestamp) if ($status); } print "\n"; } while (<>) { if (m/(\d+)\s+ms\s+(\d+)\s+ms\s+\((\d+)\s+us\s+EP\=([\da-fA-F]+).*[\<\>]+\s*(.*)/) { my $timestamp = sprintf "%09u ms %6u ms %7u us ", $1, $2, $3; my $ep = hex($4); my $payload = $5; printf("// %sEP=0x%02x: %s\n", $timestamp, $ep, $payload) if ($debug); next if (!$payload); $timestamp = "" if (!$show_timestamp); next if (!($ep == $ctrl_ep || $ep == $resp_ep)); my @bytes = split(/ /, $payload); for (my $i = 0; $i < scalar(@bytes); $i++) { $bytes[$i] = hex($bytes[$i]); } my $len = shift @bytes; my ($mbox, $cmd, $seq, $status); # Discount checksum and header length if ($ep == $ctrl_ep) { $mbox = shift @bytes; # Actually, part of CMD $cmd = shift @bytes; $seq = shift @bytes; if (defined($cmd_map{$cmd})) { $cmd = $cmd_map{$cmd}; } else { $cmd = sprintf "unknown 0x%02x", $cmd; } $len -= 4 + 1; } else { $seq = shift @bytes; $status = shift @bytes; $len -= 3 + 1; } my $checksum = pop @bytes; $checksum |= (pop @bytes) << 8; if ($ep == $ctrl_ep) { my @data = ( $timestamp, $ep, $len, $seq, $mbox, $cmd, @bytes ); push @stack, \@data; if ($cmd eq "CMD_FW_DL") { print_send_recv($timestamp, $ep, 0, 0, 0, ""); } next; } my $pay; # Print everything, except the checksum for (my $i = 0; $i < scalar(@bytes); $i++) { if (!$i) { $pay .= sprintf "0x%02x", $bytes[$i]; } else { $pay .= sprintf ", 0x%02x", $bytes[$i]; } } print_send_recv($timestamp, $ep, $len, $seq, $status, $pay); } }