33649a07ec106a305aa531b2f3655ba3d54498cf
[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 = 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 = (
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': ''},
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'),
67         ('location', 'Current location'),
68         ('function', 'Current function'),
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,)),
77         ('mode', 'Current mode', (7, 8, 9)),
78         ('location', 'Current location', (10,)),
79         ('function', 'Current function', (11,)),
80     )
81
82     def __init__(self):
83         self.buf = []
84         self.syncbuf = []
85         self.swpackets = {}
86         self.prevsample = 0
87         self.dwt_timestamp = 0
88         self.current_mode = None
89         self.file_lookup = {}
90         self.func_lookup = {}
91
92     def start(self):
93         self.out_ann = self.register(srd.OUTPUT_ANN)
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)
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
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
176
177         if new_loc is not None:
178             self.put(ss, es, self.out_ann, [10, [new_loc]])
179
180         if new_func is not None:
181             self.put(ss, es, self.out_ann, [11, [new_func]])
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 = []