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>
18 if sys
.platform
== "win32":
24 def unconfigure(self
):
33 self
.fd
= sys
.stdin
.fileno()
34 self
.default_settings
= termios
.tcgetattr(self
.fd
)
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
)
43 def unconfigure(self
):
44 termios
.tcsetattr(self
.fd
, termios
.TCSAFLUSH
, self
.default_settings
)
47 return os
.read(self
.fd
, 1)
49 sfl_prompt_req
= b
"F7: boot from serial\n"
50 sfl_prompt_ack
= b
"\x06"
52 sfl_magic_req
= b
"sL5DdSMmkekro\n"
53 sfl_magic_ack
= b
"z6IHG7cYDID6o\n"
55 sfl_payload_length
= 64#251
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"
66 sfl_ack_success
= b
"K"
67 sfl_ack_crcerror
= b
"C"
68 sfl_ack_unknown
= b
"U"
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
111 crc
= crc16_table
[((crc
>> 8) ^ d
) & 0xff] ^
(crc
<< 8)
118 self
.payload
= bytes()
120 def compute_crc(self
):
121 return crc16(self
.cmd
+ self
.payload
)
124 packet
= bytes([len(self
.payload
)])
125 packet
+= self
.compute_crc().to_bytes(2, "big")
127 packet
+= self
.payload
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]]
147 self
.reader_alive
= False
148 self
.writer_alive
= False
150 self
.prompt_detect_buffer
= bytes(len(sfl_prompt_req
))
151 self
.magic_detect_buffer
= bytes(len(sfl_magic_req
))
153 self
.console
= Console()
155 signal
.signal(signal
.SIGINT
, self
.sigint
)
156 self
.sigint_time_last
= 0
158 def open(self
, port
, baudrate
):
159 if hasattr(self
, "port"):
161 self
.port
= serial
.serial_for_url(port
, baudrate
)
164 if not hasattr(self
, "port"):
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()
178 self
.sigint_time_last
= sigint_time_current
180 def send_frame(self
, frame
):
183 self
.port
.write(frame
.encode())
185 # Get the reply from the device
186 reply
= self
.port
.read()
187 if reply
== sfl_ack_success
:
189 elif reply
== sfl_ack_crcerror
:
192 print("[LXTERM] Got unknown reply '{}' from the device, aborting.".format(reply
))
198 def upload(self
, filename
, address
):
199 f
= open(filename
, "rb")
203 print("[LXTERM] {} {} to 0x{:08x} ({} bytes)...".format(
204 "Flashing" if self
.flash
else "Uploading", filename
, address
, length
))
205 current_address
= address
210 sys
.stdout
.write("|{}>{}| {}%\r".format('=' * (20*position
//length
),
211 ' ' * (20-20*position
//length
),
212 100*position
//length
))
215 frame_data
= f
.read(min(remaining
, sfl_payload_length
-4))
217 frame
.cmd
= sfl_cmd_flash
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:
224 current_address
+= len(frame_data
)
225 position
+= len(frame_data
)
226 remaining
-= len(frame_data
)
228 elapsed
= end
- start
230 print("[LXTERM] Upload complete ({0:.1f}KB/s).".format(length
/(elapsed
*1024)))
234 print("[LXTERM] Booting the device.")
236 frame
.cmd
= sfl_cmd_jump
237 frame
.payload
= int(self
.boot_address
, 16).to_bytes(4, "big")
238 self
.send_frame(frame
)
241 print("[LXTERM] Rebooting the device.")
243 frame
.cmd
= sfl_cmd_reboot
244 self
.send_frame(frame
)
246 def detect_prompt(self
, data
):
248 self
.prompt_detect_buffer
= self
.prompt_detect_buffer
[1:] + data
249 return self
.prompt_detect_buffer
== sfl_prompt_req
253 def answer_prompt(self
):
254 print("[LXTERM] Received serial boot prompt from the device.")
255 self
.port
.write(sfl_prompt_ack
)
257 def detect_magic(self
, data
):
259 self
.magic_detect_buffer
= self
.magic_detect_buffer
[1:] + data
260 return self
.magic_detect_buffer
== sfl_magic_req
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))
271 # clear mem_regions to avoid re-flashing on next reboot(s)
272 self
.mem_regions
= {}
275 print("[LXTERM] Done.");
279 while self
.reader_alive
:
281 sys
.stdout
.buffer.write(c
)
283 if len(self
.mem_regions
):
284 if self
.serial_boot
and self
.detect_prompt(c
):
286 if self
.detect_magic(c
):
289 except serial
.SerialException
:
290 self
.reader_alive
= False
291 self
.console
.unconfigure()
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()
300 def stop_reader(self
):
301 self
.reader_alive
= False
302 self
.reader_thread
.join()
306 while self
.writer_alive
:
307 b
= self
.console
.getkey()
311 self
.port
.write(b
"\x0a")
315 self
.writer_alive
= False
316 self
.console
.unconfigure()
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()
325 def stop_writer(self
):
326 self
.writer_alive
= False
327 self
.writer_thread
.join()
330 print("[LXTERM] Starting....")
335 self
.reader_alive
= False
336 self
.writer_alive
= False
338 def join(self
, writer_only
=False):
339 self
.writer_thread
.join()
341 self
.reader_thread
.join()
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()
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()
366 if __name__
== "__main__":