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