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