]> sigrok.org Git - libsigrokdecode.git/blob - decoders/arm_etmv3/pd.py
avr_isp: Add more parts
[libsigrokdecode.git] / decoders / arm_etmv3 / 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 subprocess
22 import re
23
24 # See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'.
25 exc_names = [
26     'No exception', 'IRQ1', 'IRQ2', 'IRQ3', 'IRQ4', 'IRQ5', 'IRQ6', 'IRQ7',
27     'IRQ0', 'UsageFault', 'NMI', 'SVC', 'DebugMon', 'MemManage', 'PendSV',
28     'SysTick', 'Reserved', 'Reset', 'BusFault', 'Reserved', 'Reserved'
29 ]
30
31 for i in range(8, 496):
32     exc_names.append('IRQ%d' % i)
33
34 def parse_varint(bytes_):
35     '''Parse an integer where the top bit is the continuation bit.
36     Returns value and number of parsed bytes.'''
37     v = 0
38     for i, b in enumerate(bytes_):
39         v |= (b & 0x7F) << (i * 7)
40         if b & 0x80 == 0:
41             return v, i+1
42     return v, len(bytes_)
43
44 def parse_uint(bytes_):
45     '''Parse little-endian integer.'''
46     v = 0
47     for i, b in enumerate(bytes_):
48         v |= b << (i * 8)
49     return v
50
51 def parse_exc_info(bytes_):
52     '''Parse exception information bytes from a branch packet.'''
53     if len(bytes_) < 1:
54         return None
55
56     excv, exclen = parse_varint(bytes_)
57     if bytes_[exclen - 1] & 0x80 != 0x00:
58         return None # Exception info not complete.
59
60     if exclen == 2 and excv & (1 << 13):
61         # Exception byte 1 was skipped, fix up the decoding.
62         excv = (excv & 0x7F) | ((excv & 0x3F80) << 7)
63
64     ns = excv & 1
65     exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0)
66     cancel = (excv >> 5) & 1
67     altisa = (excv >> 6) & 1
68     hyp = (excv >> 12) & 1
69     resume = (excv >> 14) & 0x0F
70     return (ns, exc, cancel, altisa, hyp, resume)
71
72 def parse_branch_addr(bytes_, ref_addr, cpu_state, branch_enc):
73     '''Parse encoded branch address.
74        Returns addr, addrlen, cpu_state, exc_info.
75        Returns None if packet is not yet complete'''
76
77     addr, addrlen = parse_varint(bytes_)
78
79     if bytes_[addrlen - 1] & 0x80 != 0x00:
80         return None # Branch address not complete.
81
82     addr_bits = 7 * addrlen
83
84     have_exc_info = False
85     if branch_enc == 'original':
86         if addrlen == 5 and bytes_[4] & 0x40:
87             have_exc_info = True
88     elif branch_enc == 'alternative':
89         addr_bits -= 1 # Top bit of address indicates exc_info.
90         if addrlen >= 2 and addr & (1 << addr_bits):
91             have_exc_info = True
92             addr &= ~(1 << addr_bits)
93
94     exc_info = None
95     if have_exc_info:
96         exc_info = parse_exc_info(bytes_[addrlen:])
97         if exc_info is None:
98             return None # Exception info not complete.
99
100     if addrlen == 5:
101         # Possible change in CPU state.
102         if bytes_[4] & 0xB8 == 0x08:
103             cpu_state = 'arm'
104         elif bytes_[4] & 0xB0 == 0x10:
105             cpu_state = 'thumb'
106         elif bytes_[4] & 0xA0 == 0x20:
107             cpu_state = 'jazelle'
108         else:
109             raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes_[4])
110
111     # Shift the address according to current CPU state.
112     if cpu_state == 'arm':
113         addr = (addr & 0xFFFFFFFE) << 1
114         addr_bits += 1
115     elif cpu_state == 'thumb':
116         addr = addr & 0xFFFFFFFE
117     elif cpu_state == 'jazelle':
118         addr = (addr & 0xFFFFFFFFE) >> 1
119         addr_bits -= 1
120     else:
121         raise NotImplementedError('Unhandled state: ' + cpu_state)
122
123     # If the address wasn't full, fill in with the previous address.
124     if addrlen < 5:
125         addr |= ref_addr & (0xFFFFFFFF << addr_bits)
126
127     return addr, addrlen, cpu_state, exc_info
128
129 class Decoder(srd.Decoder):
130     api_version = 3
131     id = 'arm_etmv3'
132     name = 'ARM ETMv3'
133     longname = 'ARM Embedded Trace Macroblock v3'
134     desc = 'ARM ETM v3 instruction trace protocol.'
135     license = 'gplv2+'
136     inputs = ['uart']
137     outputs = []
138     tags = ['Debug/trace']
139     annotations = (
140         ('trace', 'Trace info'),
141         ('branch', 'Branch'),
142         ('exception', 'Exception'),
143         ('execution', 'Instruction execution'),
144         ('data', 'Data access'),
145         ('pc', 'Program counter'),
146         ('instr_e', 'Executed instruction'),
147         ('instr_n', 'Not executed instruction'),
148         ('source', 'Source code'),
149         ('location', 'Current location'),
150         ('function', 'Current function'),
151     )
152     annotation_rows = (
153         ('traces', 'Trace info', (0,)),
154         ('flow', 'Code flow', (1, 2, 3,)),
155         ('data-vals', 'Data access', (4,)),
156         ('pc-vals', 'Program counters', (5,)),
157         ('instructions', 'Instructions', (6, 7,)),
158         ('sources', 'Source code', (8,)),
159         ('locations', 'Current locations', (9,)),
160         ('functions', 'Current functions', (10,)),
161     )
162     options = (
163         {'id': 'objdump', 'desc': 'objdump path',
164             'default': 'arm-none-eabi-objdump'},
165         {'id': 'objdump_opts', 'desc': 'objdump options',
166             'default': '-lSC'},
167         {'id': 'elffile', 'desc': '.elf path',
168             'default': ''},
169         {'id': 'branch_enc', 'desc': 'Branch encoding',
170             'default': 'alternative', 'values': ('alternative', 'original')},
171     )
172
173     def __init__(self):
174         self.reset()
175
176     def reset(self):
177         self.buf = []
178         self.syncbuf = []
179         self.prevsample = 0
180         self.last_branch = 0
181         self.cpu_state = 'arm'
182         self.current_pc = 0
183         self.current_loc = None
184         self.current_func = None
185         self.next_instr_lookup = {}
186         self.file_lookup = {}
187         self.func_lookup = {}
188         self.disasm_lookup = {}
189         self.source_lookup = {}
190
191     def start(self):
192         self.out_ann = self.register(srd.OUTPUT_ANN)
193         self.load_objdump()
194
195     def load_objdump(self):
196         '''Parse disassembly obtained from objdump into two tables:
197         next_instr_lookup: Find the next PC addr from current PC.
198         disasm_lookup: Find the instruction text from current PC.
199         source_lookup: Find the source code line from current PC.
200         '''
201         if not (self.options['objdump'] and self.options['elffile']):
202             return
203
204         opts = [self.options['objdump']]
205         opts += self.options['objdump_opts'].split()
206         opts += [self.options['elffile']]
207
208         try:
209             disasm = subprocess.check_output(opts)
210         except subprocess.CalledProcessError:
211             return
212
213         disasm = disasm.decode('utf-8', 'replace')
214
215         instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*')
216         branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)')
217         filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?')
218         funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*')
219
220         prev_src = ''
221         prev_file = ''
222         prev_func = ''
223
224         for line in disasm.split('\n'):
225             m = instpat.match(line)
226             if m:
227                 addr = int(m.group(1), 16)
228                 raw = m.group(2)
229                 disas = m.group(3).strip().replace('\t', ' ')
230                 self.disasm_lookup[addr] = disas
231                 self.source_lookup[addr] = prev_src
232                 self.file_lookup[addr] = prev_file
233                 self.func_lookup[addr] = prev_func
234
235                 # Next address in direct sequence.
236                 ilen = len(raw.replace(' ', '')) // 2
237                 next_n = addr + ilen
238
239                 # Next address if branch is taken.
240                 bm = branchpat.match(disas)
241                 if bm:
242                     next_e = int(bm.group(2), 16)
243                 else:
244                     next_e = next_n
245
246                 self.next_instr_lookup[addr] = (next_n, next_e)
247             else:
248                 m = funcpat.match(line)
249                 if m:
250                     prev_func = m.group(1)
251                     prev_src = None
252                 else:
253                     m = filepat.match(line)
254                     if m:
255                         prev_file = m.group(1)
256                         prev_src = None
257                     else:
258                         prev_src = line.strip()
259
260     def flush_current_loc(self):
261         if self.current_loc is not None:
262             ss, es, loc, src = self.current_loc
263             if loc:
264                 self.put(ss, es, self.out_ann, [9, [loc]])
265             if src:
266                 self.put(ss, es, self.out_ann, [8, [src]])
267             self.current_loc = None
268
269     def flush_current_func(self):
270         if self.current_func is not None:
271             ss, es, func = self.current_func
272             if func:
273                 self.put(ss, es, self.out_ann, [10, [func]])
274             self.current_func = None
275
276     def instructions_executed(self, exec_status):
277         '''Advance program counter based on executed instructions.
278         Argument is a list of False for not executed and True for executed
279         instructions.
280         '''
281
282         if len(exec_status) == 0:
283             return
284
285         tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
286
287         for i, exec_status in enumerate(exec_status):
288             pc = self.current_pc
289             default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4
290             target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next))
291             ss = self.startsample + round(tdelta * i)
292             es = self.startsample + round(tdelta * (i+1))
293
294             self.put(ss, es, self.out_ann,
295                      [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
296
297             new_loc = self.file_lookup.get(pc)
298             new_src = self.source_lookup.get(pc)
299             new_dis = self.disasm_lookup.get(pc)
300             new_func = self.func_lookup.get(pc)
301
302             # Report source line only when it changes.
303             if self.current_loc is not None:
304                 if new_loc != self.current_loc[2] or new_src != self.current_loc[3]:
305                     self.flush_current_loc()
306
307             if self.current_loc is None:
308                 self.current_loc = [ss, es, new_loc, new_src]
309             else:
310                 self.current_loc[1] = es
311
312             # Report function name only when it changes.
313             if self.current_func is not None:
314                 if new_func != self.current_func[2]:
315                     self.flush_current_func()
316
317             if self.current_func is None:
318                 self.current_func = [ss, es, new_func]
319             else:
320                 self.current_func[1] = es
321
322             # Report instruction every time.
323             if new_dis:
324                 if exec_status:
325                     a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
326                 else:
327                     a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
328                 self.put(ss, es, self.out_ann, a)
329
330             if exec_status:
331                 self.current_pc = target_e
332             else:
333                 self.current_pc = target_n
334
335     def get_packet_type(self, byte):
336         '''Identify packet type based on its first byte.
337            See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types"
338         '''
339         if byte & 0x01 == 0x01:
340             return 'branch'
341         elif byte == 0x00:
342             return 'a_sync'
343         elif byte == 0x04:
344             return 'cyclecount'
345         elif byte == 0x08:
346             return 'i_sync'
347         elif byte == 0x0C:
348             return 'trigger'
349         elif byte & 0xF3 in (0x20, 0x40, 0x60):
350             return 'ooo_data'
351         elif byte == 0x50:
352             return 'store_failed'
353         elif byte == 0x70:
354             return 'i_sync'
355         elif byte & 0xDF in (0x54, 0x58, 0x5C):
356             return 'ooo_place'
357         elif byte == 0x3C:
358             return 'vmid'
359         elif byte & 0xD3 == 0x02:
360             return 'data'
361         elif byte & 0xFB == 0x42:
362             return 'timestamp'
363         elif byte == 0x62:
364             return 'data_suppressed'
365         elif byte == 0x66:
366             return 'ignore'
367         elif byte & 0xEF == 0x6A:
368             return 'value_not_traced'
369         elif byte == 0x6E:
370             return 'context_id'
371         elif byte == 0x76:
372             return 'exception_exit'
373         elif byte == 0x7E:
374             return 'exception_entry'
375         elif byte & 0x81 == 0x80:
376             return 'p_header'
377         else:
378             return 'unknown'
379
380     def fallback(self, buf):
381         ptype = self.get_packet_type(buf[0])
382         return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]]
383
384     def handle_a_sync(self, buf):
385         if buf[-1] == 0x80:
386             return [0, ['Synchronization']]
387
388     def handle_exception_exit(self, buf):
389         return [2, ['Exception exit']]
390
391     def handle_exception_entry(self, buf):
392         return [2, ['Exception entry']]
393
394     def handle_i_sync(self, buf):
395         contextid_bytes = 0 # This is the default ETM config.
396
397         if len(buf) < 6:
398             return None # Packet definitely not full yet.
399
400         if buf[0] == 0x08: # No cycle count.
401             cyclecount = None
402             idx = 1 + contextid_bytes # Index to info byte.
403         elif buf[0] == 0x70: # With cycle count.
404             cyclecount, cyclen = parse_varint(buf[1:6])
405             idx = 1 + cyclen + contextid_bytes
406
407         if len(buf) <= idx + 4:
408             return None
409         infobyte = buf[idx]
410         addr = parse_uint(buf[idx+1:idx+5])
411
412         reasoncode = (infobyte >> 5) & 3
413         reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode]
414         jazelle = (infobyte >> 4) & 1
415         nonsec = (infobyte >> 3) & 1
416         altisa = (infobyte >> 2) & 1
417         hypervisor = (infobyte >> 1) & 1
418         thumb = addr & 1
419         addr &= 0xFFFFFFFE
420
421         if reasoncode == 0 and self.current_pc != addr:
422             self.put(self.startsample, self.prevsample, self.out_ann,
423                      [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \
424                      (self.current_pc, addr)]])
425         elif reasoncode != 0:
426             # Reset location when the trace has been interrupted.
427             self.flush_current_loc()
428             self.flush_current_func()
429
430         self.last_branch = addr
431         self.current_pc = addr
432
433         if jazelle:
434             self.cpu_state = 'jazelle'
435         elif thumb:
436             self.cpu_state = 'thumb'
437         else:
438             self.cpu_state = 'arm'
439
440         cycstr = ''
441         if cyclecount is not None:
442             cycstr = ', cyclecount %d' % cyclecount
443
444         if infobyte & 0x80: # LSIP packet
445             self.put(self.startsample, self.prevsample, self.out_ann,
446                      [0, ['WARN: LSIP I-Sync packet not implemented']])
447
448         return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \
449                     (reason, addr, self.cpu_state, cycstr), \
450                     'I-Sync: %s 0x%08x' % (reason, addr)]]
451
452     def handle_trigger(self, buf):
453         return [0, ['Trigger event', 'Trigger']]
454
455     def handle_p_header(self, buf):
456         # Only non cycle-accurate mode supported.
457         if buf[0] & 0x83 == 0x80:
458             n = (buf[0] >> 6) & 1
459             e = (buf[0] >> 2) & 15
460
461             self.instructions_executed([1] * e + [0] * n)
462
463             if n:
464                 return [3, ['%d instructions executed, %d skipped due to ' \
465                             'condition codes' % (e, n),
466                             '%d ins exec, %d skipped' % (e, n),
467                             '%dE,%dN' % (e, n)]]
468             else:
469                 return [3, ['%d instructions executed' % e,
470                             '%d ins exec' % e, '%dE' % e]]
471         elif buf[0] & 0xF3 == 0x82:
472             i1 = (buf[0] >> 3) & 1
473             i2 = (buf[0] >> 2) & 1
474             self.instructions_executed([not i1, not i2])
475             txt1 = ('executed', 'skipped')
476             txt2 = ('E', 'S')
477             return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]),
478                         'I1 %s, I2 %s' % (txt2[i1], txt2[i2]),
479                         '%s,%s' % (txt2[i1], txt2[i2])]]
480         else:
481             return self.fallback(buf)
482
483     def handle_branch(self, buf):
484         if buf[-1] & 0x80 != 0x00:
485             return None # Not complete yet.
486
487         brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
488                                    self.options['branch_enc'])
489
490         if brinfo is None:
491             return None # Not complete yet.
492
493         addr, addrlen, cpu_state, exc_info = brinfo
494         self.last_branch = addr
495         self.current_pc = addr
496
497         txt = ''
498
499         if cpu_state != self.cpu_state:
500             txt += ', to %s state' % cpu_state
501             self.cpu_state = cpu_state
502
503         annidx = 1
504
505         if exc_info:
506             annidx = 2
507             ns, exc, cancel, altisa, hyp, resume = exc_info
508             if ns:
509                 txt += ', to non-secure state'
510             if exc:
511                 if exc < len(exc_names):
512                     txt += ', exception %s' % exc_names[exc]
513                 else:
514                     txt += ', exception 0x%02x' % exc
515             if cancel:
516                 txt += ', instr cancelled'
517             if altisa:
518                 txt += ', to AltISA'
519             if hyp:
520                 txt += ', to hypervisor'
521             if resume:
522                 txt += ', instr resume 0x%02x' % resume
523
524         return [annidx, ['Branch to 0x%08x%s' % (addr, txt),
525                          'B 0x%08x%s' % (addr, txt)]]
526
527     def decode(self, ss, es, data):
528         ptype, rxtx, pdata = data
529
530         if ptype != 'DATA':
531             return
532
533         # Reset packet if there is a long pause between bytes.
534         # This helps getting the initial synchronization.
535         self.byte_len = es - ss
536         if ss - self.prevsample > 16 * self.byte_len:
537             self.flush_current_loc()
538             self.flush_current_func()
539             self.buf = []
540         self.prevsample = es
541
542         self.buf.append(pdata[0])
543
544         # Store the start time of the packet.
545         if len(self.buf) == 1:
546             self.startsample = ss
547
548         # Keep separate buffer for detection of sync packets.
549         # Sync packets override everything else, so that we can regain sync
550         # even if some packets are corrupted.
551         self.syncbuf = self.syncbuf[-4:] + [pdata[0]]
552         if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]:
553             self.buf = self.syncbuf
554             self.syncbuf = []
555
556         # See if it is ready to be decoded.
557         ptype = self.get_packet_type(self.buf[0])
558         if hasattr(self, 'handle_' + ptype):
559             func = getattr(self, 'handle_' + ptype)
560             data = func(self.buf)
561         else:
562             data = self.fallback(self.buf)
563
564         if data is not None:
565             if data:
566                 self.put(self.startsample, es, self.out_ann, data)
567             self.buf = []