]> sigrok.org Git - libsigrokdecode.git/blob - decoders/arm_itm/pd.py
Add ARM TPIU/ITM/ETMv3 decoders
[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
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': 'addr2line', 'desc': 'addr2line path',
50             'default': 'arm-none-eabi-addr2line'},
51         {'id': 'addr2line_opts', 'desc': 'addr2line options',
52             'default': '-f -C -s -p'},
53         {'id': 'elffile', 'desc': '.elf path', 'default': ''},
54     )
55     annotations = (
56         ('trace', 'Trace information'),
57         ('timestamp', 'Timestamp'),
58         ('software', 'Software message'),
59         ('dwt_event', 'DWT event'),
60         ('dwt_watchpoint', 'DWT watchpoint'),
61         ('dwt_exc', 'Exception trace'),
62         ('dwt_pc', 'Program counter'),
63         ('mode_thread', 'Current mode: thread'),
64         ('mode_irq', 'Current mode: IRQ'),
65         ('mode_exc', 'Current mode: Exception'),
66         ('location', 'Current location')
67     )
68     annotation_rows = (
69         ('trace', 'Trace information', (0, 1)),
70         ('software', 'Software trace', (2,)),
71         ('dwt_event', 'DWT event', (3,)),
72         ('dwt_watchpoint', 'DWT watchpoint', (4,)),
73         ('dwt_exc', 'Exception trace', (5,)),
74         ('dwt_pc', 'Program counter', (6,)),
75         ('mode', 'Current mode', (7, 8, 9,)),
76         ('location', 'Current location', (10,)),
77     )
78
79     def __init__(self, **kwargs):
80         self.buf = []
81         self.syncbuf = []
82         self.swpackets = {}
83         self.prevsample = 0
84         self.dwt_timestamp = 0
85         self.current_mode = None
86         self.current_loc = None
87
88     def start(self):
89         self.out_ann = self.register(srd.OUTPUT_ANN)
90
91     def get_packet_type(self, byte):
92         '''Identify packet type based on its first byte.
93            See ARMv7-M_ARM.pdf section "Debug ITM and DWT" "Packet Types"
94         '''
95         if byte & 0x7F == 0:
96             return 'sync'
97         elif byte == 0x70:
98             return 'overflow'
99         elif byte & 0x0F == 0 and byte & 0xF0 != 0:
100             return 'timestamp'
101         elif byte & 0x0F == 0x08:
102             return 'sw_extension'
103         elif byte & 0x0F == 0x0C:
104             return 'hw_extension'
105         elif byte & 0x0F == 0x04:
106             return 'reserved'
107         elif byte & 0x04 == 0x00:
108             return 'software'
109         else:
110             return 'hardware'
111
112     def mode_change(self, new_mode):
113         if self.current_mode is not None:
114             start, mode = self.current_mode
115             if mode.startswith('Thread'):
116                 ann_idx = 7
117             elif mode.startswith('IRQ'):
118                 ann_idx = 8
119             else:
120                 ann_idx = 9
121             self.put(start, self.startsample, self.out_ann, [ann_idx, [mode]])
122
123         if new_mode is None:
124             self.current_mode = None
125         else:
126             self.current_mode = (self.startsample, new_mode)
127
128     def location_change(self, new_pc):
129         if self.options['addr2line'] and self.options['elffile']:
130             opts = [self.options['addr2line'], '-e', self.options['elffile']]
131             opts += self.options['addr2line_opts'].split()
132             opts += ['0x%08x' % new_pc]
133
134             try:
135                 new_loc = subprocess.check_output(opts)
136             except subprocess.CalledProcessError:
137                 return
138
139             new_loc = new_loc.decode('utf-8', 'replace').strip()
140
141             if self.current_loc is not None:
142                 start, loc = self.current_loc
143                 if loc == new_loc:
144                     return # Still on same line.
145                 self.put(start, self.startsample, self.out_ann, [10, [loc]])
146
147             self.current_loc = (self.startsample, new_loc)
148
149     def fallback(self, buf):
150         ptype = self.get_packet_type(buf[0])
151         return [0, [('Unhandled %s: ' % ptype) + ' '.join(['%02x' % b for b in buf])]]
152
153     def handle_overflow(self, buf):
154         return [0, ['Overflow']]
155
156     def handle_hardware(self, buf):
157         '''Handle packets from hardware source, i.e. DWT block.'''
158         plen = (0, 1, 2, 4)[buf[0] & 0x03]
159         pid = buf[0] >> 3
160         if len(buf) != plen + 1:
161             return None # Not complete yet.
162
163         if pid == 0:
164             text = 'DWT events:'
165             if buf[1] & 0x20:
166                 text += ' Cyc'
167             if buf[1] & 0x10:
168                 text += ' Fold'
169             if buf[1] & 0x08:
170                 text += ' LSU'
171             if buf[1] & 0x04:
172                 text += ' Sleep'
173             if buf[1] & 0x02:
174                 text += ' Exc'
175             if buf[1] & 0x01:
176                 text += ' CPI'
177             return [3, [text]]
178         elif pid == 1:
179             excnum = ((buf[2] & 1) << 8) | buf[1]
180             event = (buf[2] >> 4)
181             excstr = ARM_EXCEPTIONS.get(excnum, 'IRQ %d' % (excnum - 16))
182             if event == 1:
183                 self.mode_change(excstr)
184                 return [5, ['Enter: ' + excstr, 'E ' + excstr]]
185             elif event == 2:
186                 self.mode_change(None)
187                 return [5, ['Exit: ' + excstr, 'X ' + excstr]]
188             elif event == 3:
189                 self.mode_change(excstr)
190                 return [5, ['Resume: ' + excstr, 'R ' + excstr]]
191         elif pid == 2:
192             pc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)
193             self.location_change(pc)
194             return [6, ['PC: 0x%08x' % pc]]
195         elif (buf[0] & 0xC4) == 0x84:
196             comp = (buf[0] & 0x30) >> 4
197             what = 'Read' if (buf[0] & 0x08) == 0 else 'Write'
198             if plen == 1:
199                 data = '0x%02x' % (buf[1])
200             elif plen == 2:
201                 data = '0x%04x' % (buf[1] | (buf[2] << 8))
202             else:
203                 data = '0x%08x' % (buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24))
204             return [4, ['Watchpoint %d: %s data %s' % (comp, what, data),
205                         'WP%d: %s %s' % (comp, what[0], data)]]
206         elif (buf[0] & 0xCF) == 0x47:
207             comp = (buf[0] & 0x30) >> 4
208             addr = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)
209             self.location_change(addr)
210             return [4, ['Watchpoint %d: PC 0x%08x' % (comp, addr),
211                         'WP%d: PC 0x%08x' % (comp, addr)]]
212         elif (buf[0] & 0xCF) == 0x4E:
213             comp = (buf[0] & 0x30) >> 4
214             offset = buf[1] | (buf[2] << 8)
215             return [4, ['Watchpoint %d: address 0x????%04x' % (comp, offset),
216                         'WP%d: A 0x%04x' % (comp, offset)]]
217
218         return self.fallback(buf)
219
220     def handle_software(self, buf):
221         '''Handle packets generated by software running on the CPU.'''
222         plen = (0, 1, 2, 4)[buf[0] & 0x03]
223         pid = buf[0] >> 3
224         if len(buf) != plen + 1:
225             return None # Not complete yet.
226
227         if plen == 1 and chr(buf[1]) in string.printable:
228             self.add_delayed_sw(pid, chr(buf[1]))
229             return [] # Handled but no data to output.
230
231         self.push_delayed_sw()
232
233         if plen == 1:
234             return [2, ['%d: 0x%02x' % (pid, buf[1])]]
235         elif plen == 2:
236             return [2, ['%d: 0x%02x%02x' % (pid, buf[2], buf[1])]]
237         elif plen == 4:
238             return [2, ['%d: 0x%02x%02x%02x%02x' % (pid, buf[4], buf[3], buf[2], buf[1])]]
239
240     def handle_timestamp(self, buf):
241         '''Handle timestamp packets, which indicate the time of some DWT event packet.'''
242         if buf[-1] & 0x80 != 0:
243             return None # Not complete yet.
244
245         if buf[0] & 0x80 == 0:
246             tc = 0
247             ts = buf[0] >> 4
248         else:
249             tc = (buf[0] & 0x30) >> 4
250             ts = buf[1] & 0x7F
251             if len(buf) > 2:
252                 ts |= (buf[2] & 0x7F) << 7
253             if len(buf) > 3:
254                 ts |= (buf[3] & 0x7F) << 14
255             if len(buf) > 4:
256                 ts |= (buf[4] & 0x7F) << 21
257
258         self.dwt_timestamp += ts
259
260         if tc == 0:
261             msg = '(exact)'
262         elif tc == 1:
263             msg = '(timestamp delayed)'
264         elif tc == 2:
265             msg = '(event delayed)'
266         elif tc == 3:
267             msg = '(event and timestamp delayed)'
268
269         return [1, ['Timestamp: %d %s' % (self.dwt_timestamp, msg)]]
270
271     def add_delayed_sw(self, pid, c):
272         '''We join printable characters from software source so that printed
273         strings are easy to read. Joining is done by PID so that different
274         sources do not get confused with each other.'''
275         if self.swpackets.get(pid) is not None:
276             self.swpackets[pid][1] = self.prevsample
277             self.swpackets[pid][2] += c
278         else:
279             self.swpackets[pid] = [self.startsample, self.prevsample, c]
280
281     def push_delayed_sw(self):
282         for pid, packet in self.swpackets.items():
283             if packet is None:
284                 continue
285             ss, prevtime, text = packet
286             # Heuristic criterion: Text has ended if at least 16 byte
287             # durations after previous received byte. Actual delay depends
288             # on printf implementation on target.
289             if self.prevsample - prevtime > 16 * self.byte_len:
290                 self.put(ss, prevtime, self.out_ann, [2, ['%d: "%s"' % (pid, text)]])
291                 self.swpackets[pid] = None
292
293     def decode(self, ss, es, data):
294         ptype, rxtx, pdata = data
295
296         # For now, ignore all UART packets except the actual data packets.
297         if ptype != 'DATA':
298             return
299
300         self.byte_len = es - ss
301
302         # Reset packet if there is a long pause between bytes.
303         # TPIU framing can introduce small pauses, but more than 1 frame
304         # should reset packet.
305         if ss - self.prevsample > 16 * self.byte_len:
306             self.push_delayed_sw()
307             self.buf = []
308         self.prevsample = es
309
310         # Build up the current packet byte by byte.
311         self.buf.append(pdata[0])
312
313         # Store the start time of the packet.
314         if len(self.buf) == 1:
315             self.startsample = ss
316
317         # Keep separate buffer for detection of sync packets.
318         # Sync packets override everything else, so that we can regain sync
319         # even if some packets are corrupted.
320         self.syncbuf = self.syncbuf[-5:] + [pdata[0]]
321         if self.syncbuf == [0, 0, 0, 0, 0, 0x80]:
322             self.buf = self.syncbuf
323
324         # See if it is ready to be decoded.
325         ptype = self.get_packet_type(self.buf[0])
326         if hasattr(self, 'handle_' + ptype):
327             func = getattr(self, 'handle_' + ptype)
328             data = func(self.buf)
329         else:
330             data = self.fallback(self.buf)
331
332         if data is not None:
333             if data:
334                 self.put(self.startsample, es, self.out_ann, data)
335             self.buf = []