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.

205 lines
5.8KB

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