]> sigrok.org Git - libsigrokdecode.git/blame - decoders/arm_itm/pd.py
jtag_ejtag: Shorten variable names to ss/es.
[libsigrokdecode.git] / decoders / arm_itm / pd.py
CommitLineData
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
20import sigrokdecode as srd
21import string
22import subprocess
27c69645 23import re
686f0c36
PA
24
25ARM_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
39class 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 = []