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