Add ARM TPIU/ITM/ETMv3 decoders
authorPetteri Aimonen <jpa@git.mail.kapsi.fi>
Mon, 23 Feb 2015 17:49:06 +0000 (19:49 +0200)
committerUwe Hermann <uwe@hermann-uwe.de>
Mon, 2 Mar 2015 09:59:43 +0000 (10:59 +0100)
decoders/arm_etmv3/__init__.py [new file with mode: 0644]
decoders/arm_etmv3/pd.py [new file with mode: 0644]
decoders/arm_itm/__init__.py [new file with mode: 0644]
decoders/arm_itm/pd.py [new file with mode: 0644]
decoders/arm_tpiu/__init__.py [new file with mode: 0644]
decoders/arm_tpiu/pd.py [new file with mode: 0644]

diff --git a/decoders/arm_etmv3/__init__.py b/decoders/arm_etmv3/__init__.py
new file mode 100644 (file)
index 0000000..7955139
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+'''
+This decoder stacks on top of the 'uart' decoder and decodes packets of
+the ARMv7m Embedded Trace Macroblock v3.x.
+'''
+
+from .pd import Decoder
diff --git a/decoders/arm_etmv3/pd.py b/decoders/arm_etmv3/pd.py
new file mode 100644 (file)
index 0000000..c637142
--- /dev/null
@@ -0,0 +1,540 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+import sigrokdecode as srd
+import subprocess
+import re
+
+def parse_varint(bytes):
+    '''Parse an integer where the top bit is the continuation bit.
+    Returns value and number of parsed bytes.'''
+    v = 0
+    for i, b in enumerate(bytes):
+        v |= (b & 0x7F) << (i * 7)
+        if b & 0x80 == 0:
+            return v, i+1
+    return v, len(bytes)
+
+def parse_uint(bytes):
+    '''Parse little-endian integer.'''
+    v = 0
+    for i, b in enumerate(bytes):
+        v |= b << (i * 8)
+    return v
+
+def parse_exc_info(bytes):
+    '''Parse exception information bytes from a branch packet.'''
+    if len(bytes) < 1:
+        return None
+
+    excv, exclen = parse_varint(bytes)
+    if bytes[exclen - 1] & 0x80 != 0x00:
+        return None # Exception info not complete.
+
+    if exclen == 2 and excv & (1 << 13):
+        # Exception byte 1 was skipped, fix up the decoding.
+        excv = (excv & 0x7F) | ((excv & 0x3F80) << 7)
+
+    ns = excv & 1
+    exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0)
+    cancel = (excv >> 5) & 1
+    altisa = (excv >> 6) & 1
+    hyp = (excv >> 12) & 1
+    resume = (excv >> 14) & 0x0F
+    return (ns, exc, cancel, altisa, hyp, resume)
+
+def parse_branch_addr(bytes, ref_addr, cpu_state, branch_enc):
+    '''Parse encoded branch address.
+       Returns addr, addrlen, cpu_state, exc_info.
+       Returns None if packet is not yet complete'''
+
+    addr, addrlen = parse_varint(bytes)
+
+    if bytes[addrlen-1] & 0x80 != 0x00:
+        return None # Branch address not complete.
+
+    have_exc_info = False
+    if branch_enc == 'original':
+        if addrlen == 5 and bytes[4] & 0x40:
+            have_exc_info = True
+    elif branch_enc == 'alternative':
+        if addrlen >= 2 and bytes[addrlen - 1] & 0x40:
+            have_exc_info = True
+            addr &= ~(1 << (addrlen * 7 - 1))
+
+    exc_info = None
+    if have_exc_info:
+        exc_info = parse_exc_info(bytes[addrlen:])
+        if exc_info is None:
+            return None # Exception info not complete.
+
+    if addrlen == 5:
+        # Possible change in CPU state.
+        if bytes[4] & 0xB8 == 0x08:
+            cpu_state = 'arm'
+        elif bytes[4] & 0xB0 == 0x10:
+            cpu_state = 'thumb'
+        elif bytes[4] & 0xA0 == 0x20:
+            cpu_state = 'jazelle'
+        else:
+            raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4])
+
+    # Shift the address according to current CPU state.
+    if cpu_state == 'arm':
+        addr = (addr & 0xFFFFFFFE) << 1
+    elif cpu_state == 'thumb':
+        addr = addr & 0xFFFFFFFE
+    elif cpu_state == 'jazelle':
+        addr = (addr & 0xFFFFFFFFE) >> 1
+    else:
+        raise NotImplementedError('Unhandled state: ' + cpu_state)
+
+    # If the address wasn't full, fill in with the previous address.
+    if addrlen < 5:
+        bits = 7 * addrlen
+        addr |= ref_addr & (0xFFFFFFFF << bits)
+
+    return addr, addrlen, cpu_state, exc_info
+
+class Decoder(srd.Decoder):
+    api_version = 2
+    id = 'arm_etmv3'
+    name = 'ARM ETMv3'
+    longname = 'ARM Embedded Trace Macroblock'
+    desc = 'Decode ETM instruction trace packets.'
+    license = 'gplv2+'
+    inputs = ['uart']
+    outputs = ['arm_etmv3']
+    annotations = (
+        ('trace', 'Trace info'),
+        ('branch', 'Branches'),
+        ('exception', 'Exceptions'),
+        ('execution', 'Instruction execution'),
+        ('data', 'Data access'),
+        ('pc', 'Program counter'),
+        ('instr_e', 'Executed instructions'),
+        ('instr_n', 'Not executed instructions'),
+        ('source', 'Source code'),
+        ('location', 'Current location'),
+        ('function', 'Current function'),
+    )
+    annotation_rows = (
+        ('trace', 'Trace info', (0,)),
+        ('flow', 'Code flow', (1, 2, 3,)),
+        ('data', 'Data access', (4,)),
+        ('pc', 'Program counter', (5,)),
+        ('instruction', 'Instructions', (6, 7,)),
+        ('source', 'Source code', (8,)),
+        ('location', 'Current location', (9,)),
+        ('function', 'Current function', (10,)),
+    )
+    options = (
+        {'id': 'objdump', 'desc': 'objdump path',
+            'default': 'arm-none-eabi-objdump'},
+        {'id': 'objdump_opts', 'desc': 'objdump options',
+            'default': '-lS'},
+        {'id': 'elffile', 'desc': '.elf path',
+            'default': ''},
+        {'id': 'branch_enc', 'desc': 'Branch encoding',
+            'default': 'alternative', 'values': ('alternative', 'original')},
+    )
+
+    def __init__(self, **kwargs):
+        self.buf = []
+        self.syncbuf = []
+        self.prevsample = 0
+        self.last_branch = 0
+        self.cpu_state = 'arm'
+        self.current_pc = 0
+        self.current_loc = None
+        self.current_func = None
+        self.next_instr_lookup = {}
+        self.file_lookup = {}
+        self.func_lookup = {}
+        self.disasm_lookup = {}
+        self.source_lookup = {}
+
+    def start(self):
+        self.out_ann = self.register(srd.OUTPUT_ANN)
+        self.load_objdump()
+
+    def load_objdump(self):
+        '''Parse disassembly obtained from objdump into two tables:
+        next_instr_lookup: Find the next PC addr from current PC.
+        disasm_lookup: Find the instruction text from current PC.
+        source_lookup: Find the source code line from current PC.
+        '''
+        if not (self.options['objdump'] and self.options['elffile']):
+            return
+
+        opts = [self.options['objdump']]
+        opts += self.options['objdump_opts'].split()
+        opts += [self.options['elffile']]
+
+        try:
+            disasm = subprocess.check_output(opts)
+        except subprocess.CalledProcessError:
+            return
+
+        disasm = disasm.decode('utf-8', 'replace')
+
+        instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*')
+        branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)')
+        filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?')
+        funcpat = re.compile('[0-9a-fA-F]+\s*<([a-zA-Z0-9_-]+)>:.*')
+
+        prev_src = ''
+        prev_file = ''
+        prev_func = ''
+
+        for line in disasm.split('\n'):
+            m = instpat.match(line)
+            if m:
+                addr = int(m.group(1), 16)
+                raw = m.group(2)
+                disas = m.group(3).strip().replace('\t', ' ')
+                self.disasm_lookup[addr] = disas
+                self.source_lookup[addr] = prev_src
+                self.file_lookup[addr] = prev_file
+                self.func_lookup[addr] = prev_func
+
+                # Next address in direct sequence.
+                ilen = len(raw.replace(' ', '')) // 2
+                next_n = addr + ilen
+
+                # Next address if branch is taken.
+                bm = branchpat.match(disas)
+                if bm:
+                    next_e = int(bm.group(2), 16)
+                else:
+                    next_e = next_n
+
+                self.next_instr_lookup[addr] = (next_n, next_e)
+            else:
+                m = funcpat.match(line)
+                if m:
+                    prev_func = m.group(1)
+                else:
+                    m = filepat.match(line)
+                    if m:
+                        prev_file = m.group(1)
+                    else:
+                        prev_src = line.strip()
+
+    def flush_current_loc(self):
+        if self.current_loc is not None:
+            ss, es, loc, src = self.current_loc
+            if loc:
+                self.put(ss, es, self.out_ann, [9, [loc]])
+            if src:
+                self.put(ss, es, self.out_ann, [8, [src]])
+            self.current_loc = None
+
+    def flush_current_func(self):
+        if self.current_func is not None:
+            ss, es, func = self.current_func
+            if func:
+                self.put(ss, es, self.out_ann, [10, [func]])
+            self.current_func = None
+
+    def instructions_executed(self, exec_status):
+        '''Advance program counter based on executed instructions.
+        Argument is a list of False for not executed and True for executed
+        instructions.
+        '''
+
+        tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status))
+
+        for i, exec_status in enumerate(exec_status):
+            pc = self.current_pc
+            default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4
+            target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next))
+            ss = self.startsample + round(tdelta * i)
+            es = self.startsample + round(tdelta * (i+1))
+
+            self.put(ss, es, self.out_ann,
+                     [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]])
+
+            new_loc = self.file_lookup.get(pc)
+            new_src = self.source_lookup.get(pc)
+            new_dis = self.disasm_lookup.get(pc)
+            new_func = self.func_lookup.get(pc)
+
+            # Report source line only when it changes.
+            if self.current_loc is not None:
+                if new_loc != self.current_loc[2] or new_src != self.current_loc[3]:
+                    self.flush_current_loc()
+
+            if self.current_loc is None:
+                self.current_loc = [ss, es, new_loc, new_src]
+            else:
+                self.current_loc[1] = es
+
+            # Report function name only when it changes.
+            if self.current_func is not None:
+                if new_func != self.current_func[2]:
+                    self.flush_current_func()
+
+            if self.current_func is None:
+                self.current_func = [ss, es, new_func]
+            else:
+                self.current_func[1] = es
+
+            # Report instruction every time.
+            if new_dis:
+                if exec_status:
+                    a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]]
+                else:
+                    a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]]
+                self.put(ss, es, self.out_ann, a)
+
+            if exec_status:
+                self.current_pc = target_e
+            else:
+                self.current_pc = target_n
+
+    def get_packet_type(self, byte):
+        '''Identify packet type based on its first byte.
+           See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types"
+        '''
+        if byte & 0x01 == 0x01:
+            return 'branch'
+        elif byte == 0x00:
+            return 'a_sync'
+        elif byte == 0x04:
+            return 'cyclecount'
+        elif byte == 0x08:
+            return 'i_sync'
+        elif byte == 0x0C:
+            return 'trigger'
+        elif byte & 0xF3 in (0x20, 0x40, 0x60):
+            return 'ooo_data'
+        elif byte == 0x50:
+            return 'store_failed'
+        elif byte == 0x70:
+            return 'i_sync'
+        elif byte & 0xDF in (0x54, 0x58, 0x5C):
+            return 'ooo_place'
+        elif byte == 0x3C:
+            return 'vmid'
+        elif byte & 0xD3 == 0x02:
+            return 'data'
+        elif byte & 0xFB == 0x42:
+            return 'timestamp'
+        elif byte == 0x62:
+            return 'data_suppressed'
+        elif byte == 0x66:
+            return 'ignore'
+        elif byte & 0xEF == 0x6A:
+            return 'value_not_traced'
+        elif byte == 0x6E:
+            return 'context_id'
+        elif byte == 0x76:
+            return 'exception_exit'
+        elif byte == 0x7E:
+            return 'exception_entry'
+        elif byte & 0x81 == 0x80:
+            return 'p_header'
+        else:
+            return 'unknown'
+
+    def fallback(self, buf):
+        ptype = self.get_packet_type(buf[0])
+        return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]]
+
+    def handle_a_sync(self, buf):
+        if buf[-1] == 0x80:
+            return [0, ['Synchronization']]
+
+    def handle_exception_exit(self, buf):
+        return [2, 'Exception exit']
+
+    def handle_exception_entry(self, buf):
+        return [1, 'Exception entry']
+
+    def handle_i_sync(self, buf):
+        contextid_bytes = 0 # This is the default ETM config.
+
+        if len(buf) < 6:
+            return None # Packet definitely not full yet.
+
+        if buf[0] == 0x08: # No cycle count.
+            cyclecount = None
+            idx = 1 + contextid_bytes # Index to info byte.
+        elif buf[0] == 0x70: # With cycle count.
+            cyclecount, cyclen = parse_varint(buf[1:6])
+            idx = 1 + cyclen + contextid_bytes
+
+        if len(buf) <= idx + 4:
+            return None
+        infobyte = buf[idx]
+        addr = parse_uint(buf[idx+1:idx+5])
+
+        reasoncode = (infobyte >> 5) & 3
+        reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode]
+        jazelle = (infobyte >> 4) & 1
+        nonsec = (infobyte >> 3) & 1
+        altisa = (infobyte >> 2) & 1
+        hypervisor = (infobyte >> 1) & 1
+        thumb = addr & 1
+        addr &= 0xFFFFFFFE
+
+        if reasoncode == 0 and self.current_pc != addr:
+            self.put(self.startsample, self.prevsample, self.out_ann,
+                     [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \
+                     (self.current_pc, addr)]])
+        elif reasoncode != 0:
+            # Reset location when the trace has been interrupted.
+            self.flush_current_loc()
+            self.flush_current_func()
+
+        self.last_branch = addr
+        self.current_pc = addr
+
+        if jazelle:
+            self.cpu_state = 'jazelle'
+        elif thumb:
+            self.cpu_state = 'thumb'
+        else:
+            self.cpu_state = 'arm'
+
+        cycstr = ''
+        if cyclecount is not None:
+            cycstr = ', cyclecount %d' % cyclecount
+
+        if infobyte & 0x80: # LSIP packet
+            self.put(self.startsample, self.prevsample, self.out_ann,
+                     [0, ['WARN: LSIP I-Sync packet not implemented']])
+
+        return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \
+                    (reason, addr, self.cpu_state, cycstr), \
+                    'I-Sync: %s 0x%08x' % (reason, addr)]]
+
+    def handle_trigger(self, buf):
+        return [0, ['Trigger event', 'Trigger']]
+
+    def handle_p_header(self, buf):
+        # Only non cycle-accurate mode supported.
+        if buf[0] & 0x83 == 0x80:
+            n = (buf[0] >> 6) & 1
+            e = (buf[0] >> 2) & 15
+
+            self.instructions_executed([1] * e + [0] * n)
+
+            if n:
+                return [3, ['%d instructions executed, %d skipped due to ' \
+                            'condition codes' % (e, n),
+                            '%d ins exec, %d skipped' % (e, n),
+                            '%dE,%dN' % (e, n)]]
+            else:
+                return [3, ['%d instructions executed' % e,
+                            '%d ins exec' % e, '%dE' % e]]
+        elif buf[0] & 0xF3 == 0x82:
+            i1 = (buf[0] >> 3) & 1
+            i2 = (buf[0] >> 2) & 1
+            self.instructions_executed([not i1, not i2])
+            txt1 = ('executed', 'skipped')
+            txt2 = ('E', 'S')
+            return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]),
+                        'I1 %s, I2 %s' % (txt2[i1], txt2[i2]),
+                        '%s,%s' % (txt2[i1], txt2[i2])]]
+        else:
+            return self.fallback(buf)
+
+    def handle_branch(self, buf):
+        if buf[-1] & 0x80 != 0x00:
+            return None # Not complete yet.
+
+        brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state,
+                                   self.options['branch_enc'])
+
+        if brinfo is None:
+            return None # Not complete yet.
+
+        addr, addrlen, cpu_state, exc_info = brinfo
+        self.last_branch = addr
+        self.current_pc = addr
+
+        txt = ''
+
+        if cpu_state != self.cpu_state:
+            txt += ', to %s state' % cpu_state
+            self.cpu_state = cpu_state
+
+        if exc_info:
+            ns, exc, cancel, altisa, hyp, resume = exc_info
+            if ns:
+                txt += ', to non-secure state'
+            if exc:
+                # TODO: Parse the exception value to text.
+                txt += ', exception 0x%02x' % exc
+            if cancel:
+                txt += ', instr cancelled'
+            if altisa:
+                txt += ', to AltISA'
+            if hyp:
+                txt += ', to hypervisor'
+            if resume:
+                txt += ', instr resume 0x%02x' % resume
+
+        return [1, ['Branch to 0x%08x%s' % (addr, txt),
+                    'B 0x%08x%s' % (addr, txt)]]
+
+    def decode(self, ss, es, data):
+        ptype, rxtx, pdata = data
+
+        if ptype != 'DATA':
+            return
+
+        # Reset packet if there is a long pause between bytes.
+        # This helps getting the initial synchronization.
+        self.byte_len = es - ss
+        if ss - self.prevsample > 16 * self.byte_len:
+            self.flush_current_loc()
+            self.flush_current_func()
+            self.buf = []
+        self.prevsample = es
+
+        self.buf.append(pdata[0])
+
+        # Store the start time of the packet.
+        if len(self.buf) == 1:
+            self.startsample = ss
+
+        # Keep separate buffer for detection of sync packets.
+        # Sync packets override everything else, so that we can regain sync
+        # even if some packets are corrupted.
+        self.syncbuf = self.syncbuf[-4:] + [pdata[0]]
+        if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]:
+            self.buf = self.syncbuf
+            self.syncbuf = []
+
+        # See if it is ready to be decoded.
+        ptype = self.get_packet_type(self.buf[0])
+        if hasattr(self, 'handle_' + ptype):
+            func = getattr(self, 'handle_' + ptype)
+            data = func(self.buf)
+        else:
+            data = self.fallback(self.buf)
+
+        if data is not None:
+            if data:
+                self.put(self.startsample, es, self.out_ann, data)
+            self.buf = []
diff --git a/decoders/arm_itm/__init__.py b/decoders/arm_itm/__init__.py
new file mode 100644 (file)
index 0000000..d9789a4
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+'''
+This decoder stacks on top of the 'uart' or 'arm_tpiu' PD and decodes the 
+ARM Cortex-M processor trace data from Instrumentation Trace Macroblock.
+'''
+
+from .pd import Decoder
diff --git a/decoders/arm_itm/pd.py b/decoders/arm_itm/pd.py
new file mode 100644 (file)
index 0000000..e32cce3
--- /dev/null
@@ -0,0 +1,335 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+import sigrokdecode as srd
+import string
+import subprocess
+
+ARM_EXCEPTIONS = {
+    0: 'Thread',
+    1: 'Reset',
+    2: 'NMI',
+    3: 'HardFault',
+    4: 'MemManage',
+    5: 'BusFault',
+    6: 'UsageFault',
+    11: 'SVCall',
+    12: 'Debug Monitor',
+    14: 'PendSV',
+    15: 'SysTick',
+}
+
+class Decoder(srd.Decoder):
+    api_version = 2
+    id = 'arm_itm'
+    name = 'ARM ITM'
+    longname = 'ARM Instrumentation Trace Macroblock'
+    desc = 'Trace data from Cortex-M / ARMv7m ITM module.'
+    license = 'gplv2+'
+    inputs = ['uart']
+    outputs = ['arm_itm']
+    options = (
+        {'id': 'addr2line', 'desc': 'addr2line path',
+            'default': 'arm-none-eabi-addr2line'},
+        {'id': 'addr2line_opts', 'desc': 'addr2line options',
+            'default': '-f -C -s -p'},
+        {'id': 'elffile', 'desc': '.elf path', 'default': ''},
+    )
+    annotations = (
+        ('trace', 'Trace information'),
+        ('timestamp', 'Timestamp'),
+        ('software', 'Software message'),
+        ('dwt_event', 'DWT event'),
+        ('dwt_watchpoint', 'DWT watchpoint'),
+        ('dwt_exc', 'Exception trace'),
+        ('dwt_pc', 'Program counter'),
+        ('mode_thread', 'Current mode: thread'),
+        ('mode_irq', 'Current mode: IRQ'),
+        ('mode_exc', 'Current mode: Exception'),
+        ('location', 'Current location')
+    )
+    annotation_rows = (
+        ('trace', 'Trace information', (0, 1)),
+        ('software', 'Software trace', (2,)),
+        ('dwt_event', 'DWT event', (3,)),
+        ('dwt_watchpoint', 'DWT watchpoint', (4,)),
+        ('dwt_exc', 'Exception trace', (5,)),
+        ('dwt_pc', 'Program counter', (6,)),
+        ('mode', 'Current mode', (7, 8, 9,)),
+        ('location', 'Current location', (10,)),
+    )
+
+    def __init__(self, **kwargs):
+        self.buf = []
+        self.syncbuf = []
+        self.swpackets = {}
+        self.prevsample = 0
+        self.dwt_timestamp = 0
+        self.current_mode = None
+        self.current_loc = None
+
+    def start(self):
+        self.out_ann = self.register(srd.OUTPUT_ANN)
+
+    def get_packet_type(self, byte):
+        '''Identify packet type based on its first byte.
+           See ARMv7-M_ARM.pdf section "Debug ITM and DWT" "Packet Types"
+        '''
+        if byte & 0x7F == 0:
+            return 'sync'
+        elif byte == 0x70:
+            return 'overflow'
+        elif byte & 0x0F == 0 and byte & 0xF0 != 0:
+            return 'timestamp'
+        elif byte & 0x0F == 0x08:
+            return 'sw_extension'
+        elif byte & 0x0F == 0x0C:
+            return 'hw_extension'
+        elif byte & 0x0F == 0x04:
+            return 'reserved'
+        elif byte & 0x04 == 0x00:
+            return 'software'
+        else:
+            return 'hardware'
+
+    def mode_change(self, new_mode):
+        if self.current_mode is not None:
+            start, mode = self.current_mode
+            if mode.startswith('Thread'):
+                ann_idx = 7
+            elif mode.startswith('IRQ'):
+                ann_idx = 8
+            else:
+                ann_idx = 9
+            self.put(start, self.startsample, self.out_ann, [ann_idx, [mode]])
+
+        if new_mode is None:
+            self.current_mode = None
+        else:
+            self.current_mode = (self.startsample, new_mode)
+
+    def location_change(self, new_pc):
+        if self.options['addr2line'] and self.options['elffile']:
+            opts = [self.options['addr2line'], '-e', self.options['elffile']]
+            opts += self.options['addr2line_opts'].split()
+            opts += ['0x%08x' % new_pc]
+
+            try:
+                new_loc = subprocess.check_output(opts)
+            except subprocess.CalledProcessError:
+                return
+
+            new_loc = new_loc.decode('utf-8', 'replace').strip()
+
+            if self.current_loc is not None:
+                start, loc = self.current_loc
+                if loc == new_loc:
+                    return # Still on same line.
+                self.put(start, self.startsample, self.out_ann, [10, [loc]])
+
+            self.current_loc = (self.startsample, new_loc)
+
+    def fallback(self, buf):
+        ptype = self.get_packet_type(buf[0])
+        return [0, [('Unhandled %s: ' % ptype) + ' '.join(['%02x' % b for b in buf])]]
+
+    def handle_overflow(self, buf):
+        return [0, ['Overflow']]
+
+    def handle_hardware(self, buf):
+        '''Handle packets from hardware source, i.e. DWT block.'''
+        plen = (0, 1, 2, 4)[buf[0] & 0x03]
+        pid = buf[0] >> 3
+        if len(buf) != plen + 1:
+            return None # Not complete yet.
+
+        if pid == 0:
+            text = 'DWT events:'
+            if buf[1] & 0x20:
+                text += ' Cyc'
+            if buf[1] & 0x10:
+                text += ' Fold'
+            if buf[1] & 0x08:
+                text += ' LSU'
+            if buf[1] & 0x04:
+                text += ' Sleep'
+            if buf[1] & 0x02:
+                text += ' Exc'
+            if buf[1] & 0x01:
+                text += ' CPI'
+            return [3, [text]]
+        elif pid == 1:
+            excnum = ((buf[2] & 1) << 8) | buf[1]
+            event = (buf[2] >> 4)
+            excstr = ARM_EXCEPTIONS.get(excnum, 'IRQ %d' % (excnum - 16))
+            if event == 1:
+                self.mode_change(excstr)
+                return [5, ['Enter: ' + excstr, 'E ' + excstr]]
+            elif event == 2:
+                self.mode_change(None)
+                return [5, ['Exit: ' + excstr, 'X ' + excstr]]
+            elif event == 3:
+                self.mode_change(excstr)
+                return [5, ['Resume: ' + excstr, 'R ' + excstr]]
+        elif pid == 2:
+            pc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)
+            self.location_change(pc)
+            return [6, ['PC: 0x%08x' % pc]]
+        elif (buf[0] & 0xC4) == 0x84:
+            comp = (buf[0] & 0x30) >> 4
+            what = 'Read' if (buf[0] & 0x08) == 0 else 'Write'
+            if plen == 1:
+                data = '0x%02x' % (buf[1])
+            elif plen == 2:
+                data = '0x%04x' % (buf[1] | (buf[2] << 8))
+            else:
+                data = '0x%08x' % (buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24))
+            return [4, ['Watchpoint %d: %s data %s' % (comp, what, data),
+                        'WP%d: %s %s' % (comp, what[0], data)]]
+        elif (buf[0] & 0xCF) == 0x47:
+            comp = (buf[0] & 0x30) >> 4
+            addr = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)
+            self.location_change(addr)
+            return [4, ['Watchpoint %d: PC 0x%08x' % (comp, addr),
+                        'WP%d: PC 0x%08x' % (comp, addr)]]
+        elif (buf[0] & 0xCF) == 0x4E:
+            comp = (buf[0] & 0x30) >> 4
+            offset = buf[1] | (buf[2] << 8)
+            return [4, ['Watchpoint %d: address 0x????%04x' % (comp, offset),
+                        'WP%d: A 0x%04x' % (comp, offset)]]
+
+        return self.fallback(buf)
+
+    def handle_software(self, buf):
+        '''Handle packets generated by software running on the CPU.'''
+        plen = (0, 1, 2, 4)[buf[0] & 0x03]
+        pid = buf[0] >> 3
+        if len(buf) != plen + 1:
+            return None # Not complete yet.
+
+        if plen == 1 and chr(buf[1]) in string.printable:
+            self.add_delayed_sw(pid, chr(buf[1]))
+            return [] # Handled but no data to output.
+
+        self.push_delayed_sw()
+
+        if plen == 1:
+            return [2, ['%d: 0x%02x' % (pid, buf[1])]]
+        elif plen == 2:
+            return [2, ['%d: 0x%02x%02x' % (pid, buf[2], buf[1])]]
+        elif plen == 4:
+            return [2, ['%d: 0x%02x%02x%02x%02x' % (pid, buf[4], buf[3], buf[2], buf[1])]]
+
+    def handle_timestamp(self, buf):
+        '''Handle timestamp packets, which indicate the time of some DWT event packet.'''
+        if buf[-1] & 0x80 != 0:
+            return None # Not complete yet.
+
+        if buf[0] & 0x80 == 0:
+            tc = 0
+            ts = buf[0] >> 4
+        else:
+            tc = (buf[0] & 0x30) >> 4
+            ts = buf[1] & 0x7F
+            if len(buf) > 2:
+                ts |= (buf[2] & 0x7F) << 7
+            if len(buf) > 3:
+                ts |= (buf[3] & 0x7F) << 14
+            if len(buf) > 4:
+                ts |= (buf[4] & 0x7F) << 21
+
+        self.dwt_timestamp += ts
+
+        if tc == 0:
+            msg = '(exact)'
+        elif tc == 1:
+            msg = '(timestamp delayed)'
+        elif tc == 2:
+            msg = '(event delayed)'
+        elif tc == 3:
+            msg = '(event and timestamp delayed)'
+
+        return [1, ['Timestamp: %d %s' % (self.dwt_timestamp, msg)]]
+
+    def add_delayed_sw(self, pid, c):
+        '''We join printable characters from software source so that printed
+        strings are easy to read. Joining is done by PID so that different
+        sources do not get confused with each other.'''
+        if self.swpackets.get(pid) is not None:
+            self.swpackets[pid][1] = self.prevsample
+            self.swpackets[pid][2] += c
+        else:
+            self.swpackets[pid] = [self.startsample, self.prevsample, c]
+
+    def push_delayed_sw(self):
+        for pid, packet in self.swpackets.items():
+            if packet is None:
+                continue
+            ss, prevtime, text = packet
+            # Heuristic criterion: Text has ended if at least 16 byte
+            # durations after previous received byte. Actual delay depends
+            # on printf implementation on target.
+            if self.prevsample - prevtime > 16 * self.byte_len:
+                self.put(ss, prevtime, self.out_ann, [2, ['%d: "%s"' % (pid, text)]])
+                self.swpackets[pid] = None
+
+    def decode(self, ss, es, data):
+        ptype, rxtx, pdata = data
+
+        # For now, ignore all UART packets except the actual data packets.
+        if ptype != 'DATA':
+            return
+
+        self.byte_len = es - ss
+
+        # Reset packet if there is a long pause between bytes.
+        # TPIU framing can introduce small pauses, but more than 1 frame
+        # should reset packet.
+        if ss - self.prevsample > 16 * self.byte_len:
+            self.push_delayed_sw()
+            self.buf = []
+        self.prevsample = es
+
+        # Build up the current packet byte by byte.
+        self.buf.append(pdata[0])
+
+        # Store the start time of the packet.
+        if len(self.buf) == 1:
+            self.startsample = ss
+
+        # Keep separate buffer for detection of sync packets.
+        # Sync packets override everything else, so that we can regain sync
+        # even if some packets are corrupted.
+        self.syncbuf = self.syncbuf[-5:] + [pdata[0]]
+        if self.syncbuf == [0, 0, 0, 0, 0, 0x80]:
+            self.buf = self.syncbuf
+
+        # See if it is ready to be decoded.
+        ptype = self.get_packet_type(self.buf[0])
+        if hasattr(self, 'handle_' + ptype):
+            func = getattr(self, 'handle_' + ptype)
+            data = func(self.buf)
+        else:
+            data = self.fallback(self.buf)
+
+        if data is not None:
+            if data:
+                self.put(self.startsample, es, self.out_ann, data)
+            self.buf = []
diff --git a/decoders/arm_tpiu/__init__.py b/decoders/arm_tpiu/__init__.py
new file mode 100644 (file)
index 0000000..4958df8
--- /dev/null
@@ -0,0 +1,28 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+'''
+This decoder stacks on top of the 'uart' decoder and decodes the frame format
+of ARMv7m Trace Port Interface Unit. It filters the data coming from various
+trace sources (such as ARMv7m ITM and ETM blocks) into separate streams that
+can be further decoded by other PDs.
+'''
+
+from .pd import Decoder
diff --git a/decoders/arm_tpiu/pd.py b/decoders/arm_tpiu/pd.py
new file mode 100644 (file)
index 0000000..0e07eb4
--- /dev/null
@@ -0,0 +1,128 @@
+##
+## This file is part of the libsigrokdecode project.
+##
+## Copyright (C) 2015 Petteri Aimonen <jpa@sigrok.mail.kapsi.fi>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+##
+
+import sigrokdecode as srd
+
+class Decoder(srd.Decoder):
+    api_version = 2
+    id = 'arm_tpiu'
+    name = 'ARM TPIU'
+    longname = 'ARM Trace Port Interface Unit'
+    desc = 'Filter TPIU formatted trace data into separate data streams.'
+    license = 'gplv2+'
+    inputs = ['uart']
+    outputs = ['uart'] # Emulate uart output so that arm_itm/arm_etm can stack.
+    options = (
+        {'id': 'stream', 'desc': 'Stream index', 'default': 1},
+        {'id': 'sync_offset', 'desc': 'Initial sync offset', 'default': 0},
+    )
+    annotations = (
+        ('stream', 'Current stream'),
+        ('data', 'Stream data'),
+    )
+    annotation_rows = (
+        ('stream', 'Current stream', (0,)),
+        ('data', 'Stream data', (1,)),
+    )
+
+    def __init__(self, **kwargs):
+        self.buf = []
+        self.syncbuf = []
+        self.prevsample = 0
+        self.stream = 0
+        self.stream_ss = None
+        self.bytenum = 0
+
+    def start(self):
+        self.out_ann = self.register(srd.OUTPUT_ANN)
+        self.out_python = self.register(srd.OUTPUT_PYTHON)
+
+    def stream_changed(self, ss, stream):
+        if self.stream != stream:
+            if self.stream != 0:
+                self.put(self.stream_ss, ss, self.out_ann,
+                         [0, ["Stream %d" % self.stream, "S%d" % self.stream]])
+            self.stream = stream
+            self.stream_ss = ss
+
+    def emit_byte(self, ss, es, byte):
+        if self.stream == self.options['stream']:
+            self.put(ss, es, self.out_ann, [1, ["0x%02x" % byte]])
+            self.put(ss, es, self.out_python, ['DATA', 0, (byte, [])])
+
+    def process_frame(self, buf):
+        # Byte 15 contains the lowest bits of bytes 0, 2, ... 14.
+        lowbits = buf[15][2]
+
+        for i in range(0, 15, 2):
+            # Odd bytes can be stream ID or data.
+            delayed_stream_change = None
+            lowbit = (lowbits >> (i // 2)) & 0x01
+            if buf[i][2] & 0x01 != 0:
+                if lowbit:
+                    delayed_stream_change = buf[i][2] >> 1
+                else:
+                    self.stream_changed(buf[i][0], buf[i][2] >> 1)
+            else:
+                byte = buf[i][2] | lowbit
+                self.emit_byte(buf[i][0], buf[i][1], byte)
+
+            # Even bytes are always data.
+            if i < 14:
+                self.emit_byte(buf[i+1][0], buf[i+1][1], buf[i+1][2])
+
+            # The stream change can be delayed to occur after the data byte.
+            if delayed_stream_change is not None:
+                self.stream_changed(buf[i+1][1], delayed_stream_change)
+
+    def decode(self, ss, es, data):
+        ptype, rxtx, pdata = data
+
+        if ptype != 'DATA':
+            return
+
+        # Reset packet if there is a long pause between bytes.
+        self.byte_len = es - ss
+        if ss - self.prevsample > self.byte_len:
+            self.buf = []
+        self.prevsample = es
+
+        self.buf.append((ss, es, pdata[0]))
+        self.bytenum += 1
+
+        # Allow skipping N first bytes of the data. By adjusting the sync
+        # value, one can get initial synchronization as soon as the trace
+        # starts.
+        if self.bytenum < self.options['sync_offset']:
+            self.buf = []
+            return
+
+        # Keep separate buffer for detection of sync packets.
+        # Sync packets override everything else, so that we can regain sync
+        # even if some packets are corrupted.
+        self.syncbuf = self.syncbuf[-3:] + [pdata[0]]
+        if self.syncbuf == [0xFF, 0xFF, 0xFF, 0x7F]:
+            self.buf = []
+            self.syncbuf = []
+            return
+
+        if len(self.buf) == 16:
+            self.process_frame(self.buf)
+            self.buf = []