From: Petteri Aimonen Date: Mon, 23 Feb 2015 17:49:06 +0000 (+0200) Subject: Add ARM TPIU/ITM/ETMv3 decoders X-Git-Tag: libsigrokdecode-0.4.0~131 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=686f0c3621cd8ef7264f3e45e0218f2ebdfb612a;p=libsigrokdecode.git Add ARM TPIU/ITM/ETMv3 decoders --- diff --git a/decoders/arm_etmv3/__init__.py b/decoders/arm_etmv3/__init__.py new file mode 100644 index 0000000..7955139 --- /dev/null +++ b/decoders/arm_etmv3/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## 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 index 0000000..c637142 --- /dev/null +++ b/decoders/arm_etmv3/pd.py @@ -0,0 +1,540 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## 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 index 0000000..d9789a4 --- /dev/null +++ b/decoders/arm_itm/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## 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 index 0000000..e32cce3 --- /dev/null +++ b/decoders/arm_itm/pd.py @@ -0,0 +1,335 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## 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 index 0000000..4958df8 --- /dev/null +++ b/decoders/arm_tpiu/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## 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 index 0000000..0e07eb4 --- /dev/null +++ b/decoders/arm_tpiu/pd.py @@ -0,0 +1,128 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## 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 = []