]> sigrok.org Git - libsigrokdecode.git/blame - decoders/arm_itm/pd.py
decoders: Add/update tags for each PD.
[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']
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 = (
58 ('trace', 'Trace information'),
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 = (
72 ('trace', 'Trace information', (0, 1)),
73 ('software', 'Software trace', (2,)),
74 ('dwt_event', 'DWT event', (3,)),
75 ('dwt_watchpoint', 'DWT watchpoint', (4,)),
76 ('dwt_exc', 'Exception trace', (5,)),
77 ('dwt_pc', 'Program counter', (6,)),
27c69645 78 ('mode', 'Current mode', (7, 8, 9)),
686f0c36 79 ('location', 'Current location', (10,)),
27c69645 80 ('function', 'Current function', (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 = []