flush rx buffer when bad crc and fix frame payload length
[litex.git] / litex / tools / litex_term.py
1 #!/usr/bin/env python3
2
3 # This file is Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
4 # This file is Copyright (c) 2015 Sebastien Bourdeauducq <sb@m-labs.hk>
5 # This file is Copyright (c) 2016 whitequark <whitequark@whitequark.org>
6 # License: BSD
7
8 import sys
9 import signal
10 import os
11 import time
12 import serial
13 import threading
14 import argparse
15 import json
16
17
18 if sys.platform == "win32":
19 import msvcrt
20 class Console:
21 def configure(self):
22 pass
23
24 def unconfigure(self):
25 pass
26
27 def getkey(self):
28 return msvcrt.getch()
29 else:
30 import termios
31 class Console:
32 def __init__(self):
33 self.fd = sys.stdin.fileno()
34 self.default_settings = termios.tcgetattr(self.fd)
35
36 def configure(self):
37 settings = termios.tcgetattr(self.fd)
38 settings[3] = settings[3] & ~termios.ICANON & ~termios.ECHO
39 settings[6][termios.VMIN] = 1
40 settings[6][termios.VTIME] = 0
41 termios.tcsetattr(self.fd, termios.TCSANOW, settings)
42
43 def unconfigure(self):
44 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.default_settings)
45
46 def getkey(self):
47 return os.read(self.fd, 1)
48
49 sfl_prompt_req = b"F7: boot from serial\n"
50 sfl_prompt_ack = b"\x06"
51
52 sfl_magic_req = b"sL5DdSMmkekro\n"
53 sfl_magic_ack = b"z6IHG7cYDID6o\n"
54
55 sfl_payload_length = 64#251
56
57 # General commands
58 sfl_cmd_abort = b"\x00"
59 sfl_cmd_load = b"\x01"
60 sfl_cmd_load_no_crc = b"\x03"
61 sfl_cmd_jump = b"\x02"
62 sfl_cmd_flash = b"\x04"
63 sfl_cmd_reboot = b"\x05"
64
65 # Replies
66 sfl_ack_success = b"K"
67 sfl_ack_crcerror = b"C"
68 sfl_ack_unknown = b"U"
69 sfl_ack_error = b"E"
70
71
72 crc16_table = [
73 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
74 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
75 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
76 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
77 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
78 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
79 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
80 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
81 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
82 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
83 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
84 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
85 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
86 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
87 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
88 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
89 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
90 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
91 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
92 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
93 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
94 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
95 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
96 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
97 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
98 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
99 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
100 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
101 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
102 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
103 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
104 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
105 ]
106
107
108 def crc16(l):
109 crc = 0
110 for d in l:
111 crc = crc16_table[((crc >> 8) ^ d) & 0xff] ^ (crc << 8)
112 return crc & 0xffff
113
114
115 class SFLFrame:
116 def __init__(self):
117 self.cmd = bytes()
118 self.payload = bytes()
119
120 def compute_crc(self):
121 return crc16(self.cmd + self.payload)
122
123 def encode(self):
124 packet = bytes([len(self.payload)])
125 packet += self.compute_crc().to_bytes(2, "big")
126 packet += self.cmd
127 packet += self.payload
128 return packet
129
130
131 class LiteXTerm:
132 def __init__(self, serial_boot, kernel_image, kernel_address, json_images, no_crc, flash):
133 self.serial_boot = serial_boot
134 assert not (kernel_image is not None and json_images is not None)
135 self.mem_regions = {}
136 if kernel_image is not None:
137 self.mem_regions = {kernel_image: kernel_address}
138 self.boot_address = kernel_address
139 if json_images is not None:
140 f = open(json_images, "r")
141 self.mem_regions.update(json.load(f))
142 self.boot_address = self.mem_regions[list(self.mem_regions.keys())[-1]]
143 f.close()
144 self.no_crc = no_crc
145 self.flash = flash
146
147 self.reader_alive = False
148 self.writer_alive = False
149
150 self.prompt_detect_buffer = bytes(len(sfl_prompt_req))
151 self.magic_detect_buffer = bytes(len(sfl_magic_req))
152
153 self.console = Console()
154
155 signal.signal(signal.SIGINT, self.sigint)
156 self.sigint_time_last = 0
157
158 def open(self, port, baudrate):
159 if hasattr(self, "port"):
160 return
161 self.port = serial.serial_for_url(port, baudrate)
162
163 def close(self):
164 if not hasattr(self, "port"):
165 return
166 self.port.close()
167 del self.port
168
169 def sigint(self, sig, frame):
170 self.port.write(b"\x03")
171 sigint_time_current = time.time()
172 # Exit term if 2 CTRL-C pressed in less than 0.5s.
173 if (sigint_time_current - self.sigint_time_last < 0.5):
174 self.console.unconfigure()
175 self.close()
176 sys.exit()
177 else:
178 self.sigint_time_last = sigint_time_current
179
180 def send_frame(self, frame):
181 retry = 1
182 while retry:
183 self.port.write(frame.encode())
184 if not self.no_crc:
185 # Get the reply from the device
186 reply = self.port.read()
187 if reply == sfl_ack_success:
188 retry = 0
189 elif reply == sfl_ack_crcerror:
190 retry = 1
191 else:
192 print("[LXTERM] Got unknown reply '{}' from the device, aborting.".format(reply))
193 return 0
194 else:
195 retry = 0
196 return 1
197
198 def upload(self, filename, address):
199 f = open(filename, "rb")
200 f.seek(0, 2)
201 length = f.tell()
202 f.seek(0, 0)
203 print("[LXTERM] {} {} to 0x{:08x} ({} bytes)...".format(
204 "Flashing" if self.flash else "Uploading", filename, address, length))
205 current_address = address
206 position = 0
207 start = time.time()
208 remaining = length
209 while remaining:
210 sys.stdout.write("|{}>{}| {}%\r".format('=' * (20*position//length),
211 ' ' * (20-20*position//length),
212 100*position//length))
213 sys.stdout.flush()
214 frame = SFLFrame()
215 frame_data = f.read(min(remaining, sfl_payload_length-4))
216 if self.flash:
217 frame.cmd = sfl_cmd_flash
218 else:
219 frame.cmd = sfl_cmd_load if not self.no_crc else sfl_cmd_load_no_crc
220 frame.payload = current_address.to_bytes(4, "big")
221 frame.payload += frame_data
222 if self.send_frame(frame) == 0:
223 return
224 current_address += len(frame_data)
225 position += len(frame_data)
226 remaining -= len(frame_data)
227 end = time.time()
228 elapsed = end - start
229 f.close()
230 print("[LXTERM] Upload complete ({0:.1f}KB/s).".format(length/(elapsed*1024)))
231 return length
232
233 def boot(self):
234 print("[LXTERM] Booting the device.")
235 frame = SFLFrame()
236 frame.cmd = sfl_cmd_jump
237 frame.payload = int(self.boot_address, 16).to_bytes(4, "big")
238 self.send_frame(frame)
239
240 def reboot(self):
241 print("[LXTERM] Rebooting the device.")
242 frame = SFLFrame()
243 frame.cmd = sfl_cmd_reboot
244 self.send_frame(frame)
245
246 def detect_prompt(self, data):
247 if len(data):
248 self.prompt_detect_buffer = self.prompt_detect_buffer[1:] + data
249 return self.prompt_detect_buffer == sfl_prompt_req
250 else:
251 return False
252
253 def answer_prompt(self):
254 print("[LXTERM] Received serial boot prompt from the device.")
255 self.port.write(sfl_prompt_ack)
256
257 def detect_magic(self, data):
258 if len(data):
259 self.magic_detect_buffer = self.magic_detect_buffer[1:] + data
260 return self.magic_detect_buffer == sfl_magic_req
261 else:
262 return False
263
264 def answer_magic(self):
265 print("[LXTERM] Received firmware download request from the device.")
266 if(len(self.mem_regions)):
267 self.port.write(sfl_magic_ack)
268 for filename, base in self.mem_regions.items():
269 self.upload(filename, int(base, 16))
270 if self.flash:
271 # clear mem_regions to avoid re-flashing on next reboot(s)
272 self.mem_regions = {}
273 else:
274 self.boot()
275 print("[LXTERM] Done.");
276
277 def reader(self):
278 try:
279 while self.reader_alive:
280 c = self.port.read()
281 sys.stdout.buffer.write(c)
282 sys.stdout.flush()
283 if len(self.mem_regions):
284 if self.serial_boot and self.detect_prompt(c):
285 self.answer_prompt()
286 if self.detect_magic(c):
287 self.answer_magic()
288
289 except serial.SerialException:
290 self.reader_alive = False
291 self.console.unconfigure()
292 raise
293
294 def start_reader(self):
295 self.reader_alive = True
296 self.reader_thread = threading.Thread(target=self.reader)
297 self.reader_thread.setDaemon(True)
298 self.reader_thread.start()
299
300 def stop_reader(self):
301 self.reader_alive = False
302 self.reader_thread.join()
303
304 def writer(self):
305 try:
306 while self.writer_alive:
307 b = self.console.getkey()
308 if b == b"\x03":
309 self.stop()
310 elif b == b"\n":
311 self.port.write(b"\x0a")
312 else:
313 self.port.write(b)
314 except:
315 self.writer_alive = False
316 self.console.unconfigure()
317 raise
318
319 def start_writer(self):
320 self.writer_alive = True
321 self.writer_thread = threading.Thread(target=self.writer)
322 self.writer_thread.setDaemon(True)
323 self.writer_thread.start()
324
325 def stop_writer(self):
326 self.writer_alive = False
327 self.writer_thread.join()
328
329 def start(self):
330 print("[LXTERM] Starting....")
331 self.start_reader()
332 self.start_writer()
333
334 def stop(self):
335 self.reader_alive = False
336 self.writer_alive = False
337
338 def join(self, writer_only=False):
339 self.writer_thread.join()
340 if not writer_only:
341 self.reader_thread.join()
342
343
344 def _get_args():
345 parser = argparse.ArgumentParser()
346 parser.add_argument("port", help="serial port")
347 parser.add_argument("--speed", default=115200, help="serial baudrate")
348 parser.add_argument("--serial-boot", default=False, action='store_true',
349 help="automatically initiate serial boot")
350 parser.add_argument("--kernel", default=None, help="kernel image")
351 parser.add_argument("--kernel-adr", default="0x40000000", help="kernel address")
352 parser.add_argument("--images", default=None, help="json description of the images to load to memory")
353 parser.add_argument("--no-crc", default=False, action='store_true', help="disable CRC check (speedup serialboot)")
354 parser.add_argument("--flash", default=False, action='store_true', help="flash data with serialboot command")
355 return parser.parse_args()
356
357
358 def main():
359 args = _get_args()
360 term = LiteXTerm(args.serial_boot, args.kernel, args.kernel_adr, args.images, args.no_crc, args.flash)
361 term.open(args.port, int(float(args.speed)))
362 term.console.configure()
363 term.start()
364 term.join(True)
365
366 if __name__ == "__main__":
367 main()