Decode ARM ITM data from SWO pin output
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

220 lines
6.3 KiB

  1. #!/usr/bin/env python3
  2. # Open a serial port and decode ARM ITM trace (SWO) data sent with the
  3. # UART encoding.
  4. import sys
  5. import serial
  6. color_lookup = { "red": 31, "green": 32, "cyan": 36, "yellow": 33 }
  7. def color(name, text):
  8. return b"\033[%dm%s\033[0m" % (color_lookup[name], text)
  9. stdout = None
  10. def printf(str, *args):
  11. stdout.write(str.encode('utf-8') % args)
  12. def printf_color(name, str, *args):
  13. stdout.write(color(name, str.encode('utf-8') % args))
  14. def sprintf_color(name, str, *args):
  15. return color(name, str.encode('utf-8') % args)
  16. try:
  17. # On Unix systems, print an additional marker in the output stream
  18. # when SIGUSR1 or SIGUSR2 is received. This is just to help clarify
  19. # output when the chip is externally reset, etc.
  20. import signal
  21. def sigusr1_handler(signum, frame):
  22. printf_color("yellow", "--- mark ---\n")
  23. def sigusr2_handler(signum, frame):
  24. printf_color("yellow", "--- reset ---\n")
  25. signal.signal(signal.SIGUSR1, sigusr1_handler)
  26. signal.signal(signal.SIGUSR2, sigusr2_handler)
  27. except AttributeError as e:
  28. pass
  29. class ResyncException(Exception):
  30. pass
  31. class TimeoutException(Exception):
  32. pass
  33. class ITMParser:
  34. def __init__(self, stream):
  35. self.input_stream = stream
  36. def read_loop(self):
  37. """
  38. Yields bytes from the input stream one at a time. Raises an
  39. ResyncException if the bytes were a resync sequence, or
  40. TimeoutException if there was an input timeout.
  41. """
  42. sync_count = 0
  43. while True:
  44. data = next(self.input_stream)
  45. if data is None:
  46. raise TimeoutException()
  47. # Resync
  48. if data == 0x00:
  49. sync_count += 1
  50. elif sync_count >= 7 and data == 0x80:
  51. raise ResyncException()
  52. else:
  53. sync_count = 0
  54. yield data
  55. def process(self):
  56. """
  57. Main processing loop; restarts the parser any time we get
  58. a resync sequence.
  59. """
  60. while True:
  61. try:
  62. self.synced_stream = self.read_loop()
  63. while True:
  64. try:
  65. c = self.next()
  66. except TimeoutException as e:
  67. # Timeout for the first byte can be ignored.
  68. break
  69. try:
  70. text = self.parse(c)
  71. if text:
  72. printf("%s\n", text)
  73. except TimeoutException as e:
  74. # Timeout inside a parser should be reported.
  75. printf_color("red", "Timeout\n")
  76. break
  77. except ResyncException as e:
  78. printf_color("red", "Resync\n")
  79. def next(self):
  80. return next(self.synced_stream)
  81. def parse(self, c):
  82. """
  83. Process top-level ITM packets, returning a string that should be
  84. printed to describe it.
  85. """
  86. if c & 0x7f == 0:
  87. return None # part of sync packet
  88. if c == 0x70:
  89. return sprintf_color("yellow", "overflow")
  90. if c & 0x0f == 0x00 and c & 0x70 != 0x00:
  91. return self.parse_timestamp(c)
  92. if c & 0x0b == 0x80:
  93. return self.parse_extension(c)
  94. if c & 0x0f == 0x04:
  95. return sprintf_color("yellow", "reserved %02x", c)
  96. if c & 0x04 == 0x00 and c & 0x03 != 0:
  97. return self.parse_sw(c)
  98. if c & 0x04 == 0x04 and c & 0x03 != 0:
  99. return self.parse_hw(c)
  100. return sprintf_color("red", "unknown %02x", c)
  101. def parse_sw(self, c):
  102. """
  103. Parse SWIT packet
  104. """
  105. port = (c & 0x81) >> 3
  106. length = 1 << ((c & 0x3) - 1)
  107. payload = 0
  108. for i in range(length):
  109. payload |= self.next() << (i * 8)
  110. if port == 0 and length == 1:
  111. # Dump directly to stdout as binary data
  112. stdout.write(bytes([payload]))
  113. return None
  114. return sprintf_color('cyan', "SWIT port %d payload %0*x",
  115. port, 2 * length, payload)
  116. def parse_hw(self, c):
  117. """
  118. Parse HWIT packet
  119. """
  120. return sprintf_color("red", "TODO hw %02x", c)
  121. def parse_timestamp(self, c):
  122. """
  123. Parse timestamp packet
  124. """
  125. return sprintf_color("red", "TODO timestamp %02x", c)
  126. def parse_extension(self, c):
  127. """
  128. Parse extension packet
  129. """
  130. return sprintf_color("red", "TODO extension %02x", c)
  131. def main(argv):
  132. import argparse
  133. parser = argparse.ArgumentParser(
  134. prog = argv[0],
  135. description = "Decode ARM ITM data sent via SWO with UART encoding",
  136. formatter_class = argparse.ArgumentDefaultsHelpFormatter)
  137. parser.add_argument("--baudrate", "-b", metavar="BAUD", default=4000000,
  138. help="SWO data baudrate")
  139. parser.add_argument("device", metavar="PORT",
  140. help="Serial port for SWO input")
  141. args = parser.parse_args()
  142. # TODO: add option to backslash-escape output
  143. # TODO: add option to disable color in output
  144. # TODO: add option to control output buffering
  145. import os
  146. global stdout
  147. stdout = os.fdopen(sys.stdout.fileno(), 'wb', 0)
  148. ser = serial.Serial(args.device, args.baudrate)
  149. printf_color('green', 'ready\n')
  150. def input_stream():
  151. while True:
  152. # Read one byte with a 1s timeout.
  153. ser.timeout = 1
  154. data = ser.read(1)
  155. if len(data) == 0:
  156. # Timeout
  157. yield None
  158. continue
  159. yield data[0]
  160. # Then read as many more as there are immediately
  161. # available, and send them. This is more efficient than
  162. # reading each individual byte, when they're coming in
  163. # fast. Once they stop, we'll go back to the normal
  164. # 1 byte read with timeout.
  165. ser.timeout = 0
  166. while True:
  167. data = ser.read(65536)
  168. if len(data) == 0:
  169. break
  170. for c in data:
  171. yield c
  172. try:
  173. ITMParser(input_stream()).process()
  174. except KeyboardInterrupt:
  175. raise SystemExit(0)
  176. if __name__ == "__main__":
  177. main(sys.argv)