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