384999c36dd920b1d7e006981294b4b2f771cbc8
[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, write to the Free Software
18 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 ##
20
21 import sigrokdecode as srd
22 import string
23 import subprocess
24 import re
25
26 ARM_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
40 class 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 = (
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.buf = []
85         self.syncbuf = []
86         self.swpackets = {}
87         self.prevsample = 0
88         self.dwt_timestamp = 0
89         self.current_mode = None
90         self.file_lookup = {}
91         self.func_lookup = {}
92
93     def start(self):
94         self.out_ann = self.register(srd.OUTPUT_ANN)
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)
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
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
177
178         if new_loc is not None:
179             self.put(ss, es, self.out_ann, [10, [new_loc]])
180
181         if new_func is not None:
182             self.put(ss, es, self.out_ann, [11, [new_func]])
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 = []