blob: 43f10a9053d5a640a5b86da4131199fc8bc5e934 [file] [log] [blame]
Anton Staaf0c2626e2011-08-09 23:56:191#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Dump tpm transactions from previous run of dump_i2c.
7
8The output of dump_i2c can be used directly by piping it to dump_tpm's stdin.
9The dump_tpm input format is line oriented where each line should conform to:
10
11<time> <Read|Write> <address in 0xff format> NAK
12
13or:
14
15<time> <Read|Write> <address in 0xff format> DATA [data]+
16"""
17
18import fileinput
19import optparse
20import os
21import re
22import sys
23
24class TPMParseError(Exception):
25 """TPM parsing error exception.
26
27 This exception is raised when the parser failes to understand the input
28 provided. It records the file name and line number for later formatting
29 of an error message that includes a message that is specific to the parse
30 error.
31 """
32 def __init__(self, message):
33 """Initialize a new TPMParseError exception."""
34 self.message = message
35
36 try:
37 self.file_name = fileinput.filename()
38 self.line_number = fileinput.filelineno()
39 except RuntimeError:
40 self.file_name = '<doctest>'
41 self.line_number = 0
42
43 def __str__(self):
44 """Format the exception for user consumption."""
45 return '%s:%d: %s' % (self.file_name, self.line_number, self.message)
46
47
48class Transition:
49 """Structure describing entries in the state transition table."""
50 def __init__(self, current_state, event, next_state, action):
51 self.current_state = current_state
52 self.event = event
53 self.next_state = next_state
54 self.action = action
55
56
57class TPM:
58 """State machine that reads I2C transactions and prints TPM transactions.
59
60 TPM interprets single byte writes as precursors to reads since the first
61 byte in any write is used to set the register address for subsequent bytes
62 in the write or future reads.
63 >>> tpm = TPM()
64 >>> tpm.process('0.1 Write 0x20 DATA 0x00')
65 >>> tpm.process('0.2 Read 0x20 DATA 0x81')
66 Read reg TPM_ACCESS_0 returns 0x81
67
68 A write with multiple bytes is interpreted as a write to the register
69 addressed by the first byte in the sequence.
70 >>> tpm = TPM()
71 >>> tpm.process('0.1 Write 0x20 DATA 0x09 0x12 0x34')
72 Write reg TPM_DID_VID_0.3 with 0x12 0x34
73
74 TPM interprets a write that is not a single byte write that sets the same
75 TPM register address as an error.
76 >>> tpm = TPM()
77 >>> tpm.process('0.1 Write 0x20 DATA 0x08')
78 >>> tpm.process('0.2 Write 0x20 DATA 0x09 0x12')
79 Traceback (most recent call last):
80 ...
81 TPMParseError: <doctest>:0: Unexpected action "0.2 Write 0x20 DATA 0x09 0x12"
82
83 But if the write is a redundant setting of the TPM register address it is OK
84 >>> tpm = TPM()
85 >>> tpm.process('0.1 Write 0x20 DATA 0x08')
86 >>> tpm.process('0.2 Write 0x20 DATA 0x08')
87 """
88 SYNC = 0
89 IDLE = 1
90 READ = 2
91
92 ACTION_WRITE_MULTI = 0
93 ACTION_WRITE_SINGLE = 1
94 ACTION_READ = 2
95
96 def RegisterName(self, index):
97 register = ['TPM_ACCESS_0',
98 'TPM_STS_0',
99 'TPM_STS_0.BC_0',
100 'TPM_STS_0.BC_1',
101 'TPM_STS_0.BC_2',
102 'TPM_DATA_FIFO_0',
103 'TPM_DID_VID_0.0',
104 'TPM_DID_VID_0.1',
105 'TPM_DID_VID_0.2',
106 'TPM_DID_VID_0.3']
107
108 if index < len(register):
109 return register[index]
110 else:
111 raise TPMParseError('Unknown register index %d' % index)
112
113 def PrintWrite(self, data):
114 """Print TPM write transaction."""
115 self.register = int(data[0], 16)
116
117 print 'Write reg %s with %s' % (self.RegisterName(self.register),
118 ' '.join(data[1:]))
119
120 def RecordAddress(self, data):
121 """Record TPM register address."""
122 self.register = int(data[0], 16)
123
124 def CheckDuplicateAddress(self, data):
125 """Check that a write while waiting for a read is actually a duplicate."""
126 if self.register != int(data[0], 16):
127 raise TPMParseError('Expected "Read" action, got "Write" to new address')
128
129 def PrintRead(self, data):
130 """Print TPM read transaction."""
131 print 'Read reg %s returns %s' % (self.RegisterName(self.register),
132 ' '.join(data))
133
134 # This state transition table records the valid I2C bus actions for
135 # communicating with the TPM. And state/action pair not defined in this
136 # table is assumed to be invalid and will result in an TPMParseError being
137 # raised.
138 #
139 # The entries in this table correspond to the current state, the event
140 # parsed, the state to transition to and the function to execute on that
141 # transition. The function is passed a TPM instance and an array of the
142 # data values parsed from the event.
143 state_table = [
144 # The initial section of the state transition table describes the
145 # synchronization process. For the TPM this just involves waiting for
146 # the first write operation.
147 Transition(SYNC, ACTION_WRITE_MULTI, IDLE, PrintWrite),
148 Transition(SYNC, ACTION_WRITE_SINGLE, READ, RecordAddress),
149 Transition(SYNC, ACTION_READ, SYNC, None),
150
151 # After syncronization is complete the rest of the table describes the
152 # expected transitions.
153 Transition(IDLE, ACTION_WRITE_MULTI, IDLE, PrintWrite),
154 Transition(IDLE, ACTION_WRITE_SINGLE, READ, RecordAddress),
155 Transition(READ, ACTION_WRITE_SINGLE, READ, CheckDuplicateAddress),
156 Transition(READ, ACTION_READ, IDLE, PrintRead)]
157
158 def __init__(self):
159 """Initialize a new TPM instance.
160
161 >>> tpm = TPM()
162 >>> tpm.state == tpm.SYNC
163 True
164 """
165 self.state = self.SYNC
166 self.register = 0x00
167
168 def process(self, line):
169 """Update TPM state machine for one line generated by dump_i2c.
170
171 These examples show how process effects the internal state of TPM.
172 >>> tpm = TPM()
173
174 >>> tpm.process('0.1 Write 0x20 DATA 0x00')
175 >>> tpm.register == 0x00
176 True
177 >>> tpm.state == tpm.READ
178 True
179
180 >>> tpm.process('0.2 Read 0x20 DATA 0x81')
181 Read reg TPM_ACCESS_0 returns 0x81
182 >>> tpm.state == tpm.IDLE
183 True
184
185 >>> tpm.process('0.10000000 Write 0x20 DATA 0x09 0x12 0x34')
186 Write reg TPM_DID_VID_0.3 with 0x12 0x34
187 >>> tpm.register == 0x09
188 True
189 >>> tpm.state == tpm.IDLE
190 True
191 """
192 values = line.split()
193 time = float(values[0])
194 action = ' '.join(values[1].split())
195 address = ' '.join(values[2].split())
196 nak = (values[3].split()[0] == 'NAK')
197 data = values[4:]
198
199 if nak:
200 return
201
202 if action == 'Read':
203 action_index = self.ACTION_READ
204 elif action == 'Write' and len(data) == 1:
205 action_index = self.ACTION_WRITE_SINGLE
206 elif action == 'Write' and len(data) > 1:
207 action_index = self.ACTION_WRITE_MULTI
208 else:
209 raise TPMParseError('Unknown action "%s"' % action)
210
211 # Search the transition table for a matching state/action pair.
212 for transition in self.state_table:
213 if (transition.current_state == self.state and
214 transition.event == action_index):
215 if transition.action:
216 transition.action(self, data)
217
218 self.state = transition.next_state
219 break
220 else:
221 raise TPMParseError('Unexpected action "%s"' % line)
222
223
224def main():
225 tpm = TPM()
226
227 for line in fileinput.input():
228 try:
229 tpm.process(line)
230 except (TPMParseError, ValueError, IndexError) as error:
231 print error
232 return
233
234
235def Test():
236 """Run any built-in tests."""
237 import doctest
Vadim Bendebury9ddeee12013-02-14 23:24:17238 assert doctest.testmod().failed == 0
Anton Staaf0c2626e2011-08-09 23:56:19239
240
241if __name__ == '__main__':
242 # If first argument is --test, run testing code.
243 if sys.argv[1:2] == ['--test']:
244 Test()
245 else:
246 main()