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