627612f0d1a06f99a79b403f4557370c24844f71
[libsigrokdecode.git] / decoders / arm_itm / pd.py
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, see <http://www.gnu.org/licenses/>.
18 ##
19
20 import sigrokdecode as srd
21 import string
22 import subprocess
23 import re
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):
40     api_version = 3
41     id = 'arm_itm'
42     name = 'ARM ITM'
43     longname = 'ARM Instrumentation Trace Macroblock'
44     desc = 'ARM Cortex-M / ARMv7m ITM trace protocol.'
45     license = 'gplv2+'
46     inputs = ['uart']
47     outputs = ['arm_itm']
48     tags = ['Debug/trace']
49     options = (
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': ''},
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'),
68         ('location', 'Current location'),
69         ('function', 'Current function'),
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,)),
78         ('mode', 'Current mode', (7, 8, 9)),
79         ('location', 'Current location', (10,)),
80         ('function', 'Current function', (11,)),
81     )
82
83     def __init__(self):
84         self.reset()
85
86     def reset(self):
87         self.buf = []
88         self.syncbuf = []
89         self.swpackets = {}
90         self.prevsample = 0
91         self.dwt_timestamp = 0
92         self.current_mode = None
93         self.file_lookup = {}
94         self.func_lookup = {}
95
96     def start(self):
97         self.out_ann = self.register(srd.OUTPUT_ANN)
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)
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
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
180
181         if new_loc is not None:
182             self.put(ss, es, self.out_ann, [10, [new_loc]])
183
184         if new_func is not None:
185             self.put(ss, es, self.out_ann, [11, [new_func]])
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 = []