]>
Commit | Line | Data |
---|---|---|
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 = [] |