diff options
author | Petteri Aimonen <jpa@git.mail.kapsi.fi> | 2015-02-23 19:49:06 +0200 |
---|---|---|
committer | Uwe Hermann <uwe@hermann-uwe.de> | 2015-03-02 10:59:43 +0100 |
commit | 686f0c3621cd8ef7264f3e45e0218f2ebdfb612a (patch) | |
tree | 743d5d8f624ad9ff9c8b4b4a9c1891e90e4f5fe2 /decoders/arm_itm | |
parent | 5dc48752efc84171dece2dcef93577e5483b9775 (diff) | |
download | libsigrokdecode-686f0c3621cd8ef7264f3e45e0218f2ebdfb612a.tar.gz libsigrokdecode-686f0c3621cd8ef7264f3e45e0218f2ebdfb612a.zip |
Add ARM TPIU/ITM/ETMv3 decoders
Diffstat (limited to 'decoders/arm_itm')
-rw-r--r-- | decoders/arm_itm/__init__.py | 26 | ||||
-rw-r--r-- | decoders/arm_itm/pd.py | 335 |
2 files changed, 361 insertions, 0 deletions
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 <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 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 <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 = [] |