Anton Staaf | 0c2626e | 2011-08-09 23:56:19 | [diff] [blame] | 1 | #!/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 | |
| 8 | The output of dump_i2c can be used directly by piping it to dump_tpm's stdin. |
| 9 | The dump_tpm input format is line oriented where each line should conform to: |
| 10 | |
| 11 | <time> <Read|Write> <address in 0xff format> NAK |
| 12 | |
| 13 | or: |
| 14 | |
| 15 | <time> <Read|Write> <address in 0xff format> DATA [data]+ |
| 16 | """ |
| 17 | |
| 18 | import fileinput |
| 19 | import optparse |
| 20 | import os |
| 21 | import re |
| 22 | import sys |
| 23 | |
| 24 | class 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 | |
| 48 | class 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 | |
| 57 | class 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 | |
| 224 | def 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 | |
| 235 | def Test(): |
| 236 | """Run any built-in tests.""" |
| 237 | import doctest |
Vadim Bendebury | 9ddeee1 | 2013-02-14 23:24:17 | [diff] [blame] | 238 | assert doctest.testmod().failed == 0 |
Anton Staaf | 0c2626e | 2011-08-09 23:56:19 | [diff] [blame] | 239 | |
| 240 | |
| 241 | if __name__ == '__main__': |
| 242 | # If first argument is --test, run testing code. |
| 243 | if sys.argv[1:2] == ['--test']: |
| 244 | Test() |
| 245 | else: |
| 246 | main() |