]>
Commit | Line | Data |
---|---|---|
686f0c36 PA |
1 | ## |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi> | |
5 | ## | |
6 | ## This program is free software; you can redistribute it and/or modify | |
7 | ## it under the terms of the GNU General Public License as published by | |
8 | ## the Free Software Foundation; either version 2 of the License, or | |
9 | ## (at your option) any later version. | |
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 | ## You should have received a copy of the GNU General Public License | |
4539e9ca | 17 | ## along with this program; if not, see <http://www.gnu.org/licenses/>. |
686f0c36 PA |
18 | ## |
19 | ||
20 | import sigrokdecode as srd | |
21 | import string | |
22 | import subprocess | |
27c69645 | 23 | import re |
686f0c36 PA |
24 | |
25 | ARM_EXCEPTIONS = { | |
26 | 0: 'Thread', | |
27 | 1: 'Reset', | |
28 | 2: 'NMI', | |
29 | 3: 'HardFault', | |
30 | 4: 'MemManage', | |
31 | 5: 'BusFault', | |
32 | 6: 'UsageFault', | |
33 | 11: 'SVCall', | |
34 | 12: 'Debug Monitor', | |
35 | 14: 'PendSV', | |
36 | 15: 'SysTick', | |
37 | } | |
38 | ||
39 | class Decoder(srd.Decoder): | |
b197383c | 40 | api_version = 3 |
686f0c36 PA |
41 | id = 'arm_itm' |
42 | name = 'ARM ITM' | |
43 | longname = 'ARM Instrumentation Trace Macroblock' | |
44 | desc = 'Trace data from Cortex-M / ARMv7m ITM module.' | |
45 | license = 'gplv2+' | |
46 | inputs = ['uart'] | |
47 | outputs = ['arm_itm'] | |
48 | options = ( | |
27c69645 PA |
49 | {'id': 'objdump', 'desc': 'objdump path', |
50 | 'default': 'arm-none-eabi-objdump'}, | |
51 | {'id': 'objdump_opts', 'desc': 'objdump options', | |
52 | 'default': '-lSC'}, | |
53 | {'id': 'elffile', 'desc': '.elf path', | |
54 | 'default': ''}, | |
686f0c36 PA |
55 | ) |
56 | annotations = ( | |
57 | ('trace', 'Trace information'), | |
58 | ('timestamp', 'Timestamp'), | |
59 | ('software', 'Software message'), | |
60 | ('dwt_event', 'DWT event'), | |
61 | ('dwt_watchpoint', 'DWT watchpoint'), | |
62 | ('dwt_exc', 'Exception trace'), | |
63 | ('dwt_pc', 'Program counter'), | |
64 | ('mode_thread', 'Current mode: thread'), | |
65 | ('mode_irq', 'Current mode: IRQ'), | |
66 | ('mode_exc', 'Current mode: Exception'), | |
27c69645 PA |
67 | ('location', 'Current location'), |
68 | ('function', 'Current function'), | |
686f0c36 PA |
69 | ) |
70 | annotation_rows = ( | |
71 | ('trace', 'Trace information', (0, 1)), | |
72 | ('software', 'Software trace', (2,)), | |
73 | ('dwt_event', 'DWT event', (3,)), | |
74 | ('dwt_watchpoint', 'DWT watchpoint', (4,)), | |
75 | ('dwt_exc', 'Exception trace', (5,)), | |
76 | ('dwt_pc', 'Program counter', (6,)), | |
27c69645 | 77 | ('mode', 'Current mode', (7, 8, 9)), |
686f0c36 | 78 | ('location', 'Current location', (10,)), |
27c69645 | 79 | ('function', 'Current function', (11,)), |
686f0c36 PA |
80 | ) |
81 | ||
92b7b49f | 82 | def __init__(self): |
10aeb8ea GS |
83 | self.reset() |
84 | ||
85 | def reset(self): | |
686f0c36 PA |
86 | self.buf = [] |
87 | self.syncbuf = [] | |
88 | self.swpackets = {} | |
89 | self.prevsample = 0 | |
90 | self.dwt_timestamp = 0 | |
91 | self.current_mode = None | |
27c69645 PA |
92 | self.file_lookup = {} |
93 | self.func_lookup = {} | |
686f0c36 PA |
94 | |
95 | def start(self): | |
96 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
27c69645 PA |
97 | self.load_objdump() |
98 | ||
99 | def load_objdump(self): | |
100 | '''Parse disassembly obtained from objdump into a lookup tables''' | |
101 | if not (self.options['objdump'] and self.options['elffile']): | |
102 | return | |
103 | ||
104 | opts = [self.options['objdump']] | |
105 | opts += self.options['objdump_opts'].split() | |
106 | opts += [self.options['elffile']] | |
107 | ||
108 | try: | |
109 | disasm = subprocess.check_output(opts) | |
110 | except subprocess.CalledProcessError: | |
111 | return | |
112 | ||
113 | disasm = disasm.decode('utf-8', 'replace') | |
114 | ||
115 | instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') | |
116 | filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') | |
117 | funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*') | |
118 | ||
119 | prev_file = '' | |
120 | prev_func = '' | |
121 | ||
122 | for line in disasm.split('\n'): | |
123 | m = instpat.match(line) | |
124 | if m: | |
125 | addr = int(m.group(1), 16) | |
126 | self.file_lookup[addr] = prev_file | |
127 | self.func_lookup[addr] = prev_func | |
128 | else: | |
129 | m = funcpat.match(line) | |
130 | if m: | |
131 | prev_func = m.group(1) | |
132 | else: | |
133 | m = filepat.match(line) | |
134 | if m: | |
135 | prev_file = m.group(1) | |
686f0c36 PA |
136 | |
137 | def get_packet_type(self, byte): | |
138 | '''Identify packet type based on its first byte. | |
139 | See ARMv7-M_ARM.pdf section "Debug ITM and DWT" "Packet Types" | |
140 | ''' | |
141 | if byte & 0x7F == 0: | |
142 | return 'sync' | |
143 | elif byte == 0x70: | |
144 | return 'overflow' | |
145 | elif byte & 0x0F == 0 and byte & 0xF0 != 0: | |
146 | return 'timestamp' | |
147 | elif byte & 0x0F == 0x08: | |
148 | return 'sw_extension' | |
149 | elif byte & 0x0F == 0x0C: | |
150 | return 'hw_extension' | |
151 | elif byte & 0x0F == 0x04: | |
152 | return 'reserved' | |
153 | elif byte & 0x04 == 0x00: | |
154 | return 'software' | |
155 | else: | |
156 | return 'hardware' | |
157 | ||
158 | def mode_change(self, new_mode): | |
159 | if self.current_mode is not None: | |
160 | start, mode = self.current_mode | |
161 | if mode.startswith('Thread'): | |
162 | ann_idx = 7 | |
163 | elif mode.startswith('IRQ'): | |
164 | ann_idx = 8 | |
165 | else: | |
166 | ann_idx = 9 | |
167 | self.put(start, self.startsample, self.out_ann, [ann_idx, [mode]]) | |
168 | ||
169 | if new_mode is None: | |
170 | self.current_mode = None | |
171 | else: | |
172 | self.current_mode = (self.startsample, new_mode) | |
173 | ||
27c69645 PA |
174 | def location_change(self, pc): |
175 | new_loc = self.file_lookup.get(pc) | |
176 | new_func = self.func_lookup.get(pc) | |
177 | ss = self.startsample | |
178 | es = self.prevsample | |
686f0c36 | 179 | |
27c69645 PA |
180 | if new_loc is not None: |
181 | self.put(ss, es, self.out_ann, [10, [new_loc]]) | |
686f0c36 | 182 | |
27c69645 PA |
183 | if new_func is not None: |
184 | self.put(ss, es, self.out_ann, [11, [new_func]]) | |
686f0c36 PA |
185 | |
186 | def fallback(self, buf): | |
187 | ptype = self.get_packet_type(buf[0]) | |
188 | return [0, [('Unhandled %s: ' % ptype) + ' '.join(['%02x' % b for b in buf])]] | |
189 | ||
190 | def handle_overflow(self, buf): | |
191 | return [0, ['Overflow']] | |
192 | ||
193 | def handle_hardware(self, buf): | |
194 | '''Handle packets from hardware source, i.e. DWT block.''' | |
195 | plen = (0, 1, 2, 4)[buf[0] & 0x03] | |
196 | pid = buf[0] >> 3 | |
197 | if len(buf) != plen + 1: | |
198 | return None # Not complete yet. | |
199 | ||
200 | if pid == 0: | |
201 | text = 'DWT events:' | |
202 | if buf[1] & 0x20: | |
203 | text += ' Cyc' | |
204 | if buf[1] & 0x10: | |
205 | text += ' Fold' | |
206 | if buf[1] & 0x08: | |
207 | text += ' LSU' | |
208 | if buf[1] & 0x04: | |
209 | text += ' Sleep' | |
210 | if buf[1] & 0x02: | |
211 | text += ' Exc' | |
212 | if buf[1] & 0x01: | |
213 | text += ' CPI' | |
214 | return [3, [text]] | |
215 | elif pid == 1: | |
216 | excnum = ((buf[2] & 1) << 8) | buf[1] | |
217 | event = (buf[2] >> 4) | |
218 | excstr = ARM_EXCEPTIONS.get(excnum, 'IRQ %d' % (excnum - 16)) | |
219 | if event == 1: | |
220 | self.mode_change(excstr) | |
221 | return [5, ['Enter: ' + excstr, 'E ' + excstr]] | |
222 | elif event == 2: | |
223 | self.mode_change(None) | |
224 | return [5, ['Exit: ' + excstr, 'X ' + excstr]] | |
225 | elif event == 3: | |
226 | self.mode_change(excstr) | |
227 | return [5, ['Resume: ' + excstr, 'R ' + excstr]] | |
228 | elif pid == 2: | |
229 | pc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24) | |
230 | self.location_change(pc) | |
231 | return [6, ['PC: 0x%08x' % pc]] | |
232 | elif (buf[0] & 0xC4) == 0x84: | |
233 | comp = (buf[0] & 0x30) >> 4 | |
234 | what = 'Read' if (buf[0] & 0x08) == 0 else 'Write' | |
235 | if plen == 1: | |
236 | data = '0x%02x' % (buf[1]) | |
237 | elif plen == 2: | |
238 | data = '0x%04x' % (buf[1] | (buf[2] << 8)) | |
239 | else: | |
240 | data = '0x%08x' % (buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)) | |
241 | return [4, ['Watchpoint %d: %s data %s' % (comp, what, data), | |
242 | 'WP%d: %s %s' % (comp, what[0], data)]] | |
243 | elif (buf[0] & 0xCF) == 0x47: | |
244 | comp = (buf[0] & 0x30) >> 4 | |
245 | addr = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24) | |
246 | self.location_change(addr) | |
247 | return [4, ['Watchpoint %d: PC 0x%08x' % (comp, addr), | |
248 | 'WP%d: PC 0x%08x' % (comp, addr)]] | |
249 | elif (buf[0] & 0xCF) == 0x4E: | |
250 | comp = (buf[0] & 0x30) >> 4 | |
251 | offset = buf[1] | (buf[2] << 8) | |
252 | return [4, ['Watchpoint %d: address 0x????%04x' % (comp, offset), | |
253 | 'WP%d: A 0x%04x' % (comp, offset)]] | |
254 | ||
255 | return self.fallback(buf) | |
256 | ||
257 | def handle_software(self, buf): | |
258 | '''Handle packets generated by software running on the CPU.''' | |
259 | plen = (0, 1, 2, 4)[buf[0] & 0x03] | |
260 | pid = buf[0] >> 3 | |
261 | if len(buf) != plen + 1: | |
262 | return None # Not complete yet. | |
263 | ||
264 | if plen == 1 and chr(buf[1]) in string.printable: | |
265 | self.add_delayed_sw(pid, chr(buf[1])) | |
266 | return [] # Handled but no data to output. | |
267 | ||
268 | self.push_delayed_sw() | |
269 | ||
270 | if plen == 1: | |
271 | return [2, ['%d: 0x%02x' % (pid, buf[1])]] | |
272 | elif plen == 2: | |
273 | return [2, ['%d: 0x%02x%02x' % (pid, buf[2], buf[1])]] | |
274 | elif plen == 4: | |
275 | return [2, ['%d: 0x%02x%02x%02x%02x' % (pid, buf[4], buf[3], buf[2], buf[1])]] | |
276 | ||
277 | def handle_timestamp(self, buf): | |
278 | '''Handle timestamp packets, which indicate the time of some DWT event packet.''' | |
279 | if buf[-1] & 0x80 != 0: | |
280 | return None # Not complete yet. | |
281 | ||
282 | if buf[0] & 0x80 == 0: | |
283 | tc = 0 | |
284 | ts = buf[0] >> 4 | |
285 | else: | |
286 | tc = (buf[0] & 0x30) >> 4 | |
287 | ts = buf[1] & 0x7F | |
288 | if len(buf) > 2: | |
289 | ts |= (buf[2] & 0x7F) << 7 | |
290 | if len(buf) > 3: | |
291 | ts |= (buf[3] & 0x7F) << 14 | |
292 | if len(buf) > 4: | |
293 | ts |= (buf[4] & 0x7F) << 21 | |
294 | ||
295 | self.dwt_timestamp += ts | |
296 | ||
297 | if tc == 0: | |
298 | msg = '(exact)' | |
299 | elif tc == 1: | |
300 | msg = '(timestamp delayed)' | |
301 | elif tc == 2: | |
302 | msg = '(event delayed)' | |
303 | elif tc == 3: | |
304 | msg = '(event and timestamp delayed)' | |
305 | ||
306 | return [1, ['Timestamp: %d %s' % (self.dwt_timestamp, msg)]] | |
307 | ||
308 | def add_delayed_sw(self, pid, c): | |
309 | '''We join printable characters from software source so that printed | |
310 | strings are easy to read. Joining is done by PID so that different | |
311 | sources do not get confused with each other.''' | |
312 | if self.swpackets.get(pid) is not None: | |
313 | self.swpackets[pid][1] = self.prevsample | |
314 | self.swpackets[pid][2] += c | |
315 | else: | |
316 | self.swpackets[pid] = [self.startsample, self.prevsample, c] | |
317 | ||
318 | def push_delayed_sw(self): | |
319 | for pid, packet in self.swpackets.items(): | |
320 | if packet is None: | |
321 | continue | |
322 | ss, prevtime, text = packet | |
323 | # Heuristic criterion: Text has ended if at least 16 byte | |
324 | # durations after previous received byte. Actual delay depends | |
325 | # on printf implementation on target. | |
326 | if self.prevsample - prevtime > 16 * self.byte_len: | |
327 | self.put(ss, prevtime, self.out_ann, [2, ['%d: "%s"' % (pid, text)]]) | |
328 | self.swpackets[pid] = None | |
329 | ||
330 | def decode(self, ss, es, data): | |
331 | ptype, rxtx, pdata = data | |
332 | ||
333 | # For now, ignore all UART packets except the actual data packets. | |
334 | if ptype != 'DATA': | |
335 | return | |
336 | ||
337 | self.byte_len = es - ss | |
338 | ||
339 | # Reset packet if there is a long pause between bytes. | |
340 | # TPIU framing can introduce small pauses, but more than 1 frame | |
341 | # should reset packet. | |
342 | if ss - self.prevsample > 16 * self.byte_len: | |
343 | self.push_delayed_sw() | |
344 | self.buf = [] | |
345 | self.prevsample = es | |
346 | ||
347 | # Build up the current packet byte by byte. | |
348 | self.buf.append(pdata[0]) | |
349 | ||
350 | # Store the start time of the packet. | |
351 | if len(self.buf) == 1: | |
352 | self.startsample = ss | |
353 | ||
354 | # Keep separate buffer for detection of sync packets. | |
355 | # Sync packets override everything else, so that we can regain sync | |
356 | # even if some packets are corrupted. | |
357 | self.syncbuf = self.syncbuf[-5:] + [pdata[0]] | |
358 | if self.syncbuf == [0, 0, 0, 0, 0, 0x80]: | |
359 | self.buf = self.syncbuf | |
360 | ||
361 | # See if it is ready to be decoded. | |
362 | ptype = self.get_packet_type(self.buf[0]) | |
363 | if hasattr(self, 'handle_' + ptype): | |
364 | func = getattr(self, 'handle_' + ptype) | |
365 | data = func(self.buf) | |
366 | else: | |
367 | data = self.fallback(self.buf) | |
368 | ||
369 | if data is not None: | |
370 | if data: | |
371 | self.put(self.startsample, es, self.out_ann, data) | |
372 | self.buf = [] |