Tera Term is a mature and still developed Open Source Terminal Emulator for Windows. It has its roots in the Windows 3.x days, and has been rewritten from Pascal to C and some C++. This plugin adds some Rust to the mix :). It augments the serial boot protocol used for rapid firmware development in the LiteX FPGA/SoC ecosystem.
The plugin is called TTXLiteX.dll
to conform to Tera Term's
naming conventions.
The 3-Clause BSD License conforms to what most Tera Term plugins
seem to use.
The following GIF was created by running Tera Term's ttyrec-compatible recorder, and does not necessarily reflect the fonts/glyphs used by Tera Term.
There are two installers; the DLL is identical in each except for default install options (install as part of Tera Term 4 or 5). The installer will install the plugin into the Tera Term install directory. I also provide a ZIP file with the installers, the DLL itself, and README.md, CHANGELOG.md, etc. If you have a portable Tera Term, you can just copy the DLL itself to the same directory where the Tera Term binary lives and the plugin should "just work".
Only 32-bit DLLs are provided, because only 32-bit binary releases of Tera Term are provided at this time. However, I have confirmed that Tera Term, as well as my plugin, do build and work just fine for 64-bit Windows, using MinGW64/MSYS2.
Once you start Tera Term after installation, you will be greeted with a new file transfer option called "LiteX" under the File > Transfer submenu:
When you click "LiteX" under the File > Transfer submenu, you will be greeted with the following dialog:
The "File" and "Boot Address" text edits correspond to the --kernel
and
--kernel-adr
parameters of litex-term
.
The "File" need not exist until the actual transfer takes place; clicking the
elipses will bring up an Open File for convenience. Unless you have good reason
to believe otherwise1, the "Boot Address" field should be set to
"the beginning of the memory region used for the Memtest during LiteX
Initialization":
In the case of the above picture, "Boot Address" should be set to 0x21010000
,
or some equivalent decimal or hexadecimal number (underscores allowed for
formatting purposes).
Once "Active" is checked and you click "OK", the plugin will begin to look to start an SFL transfer.
Because Windows doesn't like it when you modify a file that's in use, the file is only opened just before a transfer takes place. This allows you to swap out a fresh new copy of your firmware to upload without worrying that the compile will fail because the file is busy2.
Once an SFL transfer has been requested using the above dialog, the plugin uses an FSM implementing the SFL protocol to send a file to a receiver. The plugin opens the file to send as late as possible, after the magic string has been detected, but before the plugin sends a response. This way, if there was an error opening the file, the plugin will back out of the transfer.
Starting a SFL Protocol transfer relies on an In-band command-response to start a transfer:
- A terminal application listen for the byte string
sL5DdSMmkekro\n
in order from a device that wishes to receives a file. - When a terminal detects the above string, send the byte string
z6IHG7cYDID6o\n
in response3.
While theoretically possible without any extra user intervention, a terminal
program is unlikely to send the string z6IHG7cYDID6o\n
in response to
receiving the bytes sL5DdSMmkekro\n
as part of typical user operation. Thus,
SFL assumes that if the firmware receiver detects the correct response, the
user has explicitly set up their terminal to upload a firmware to the other
side of the link. In the case of the TTXLiteX plugin, committing to an upload
is indicated by checking the "Active" box and clicking "OK" in the dialog.
Once the initial command-response has been negotiated, an SFL transfer consists of sending Type-Length-Value packets in order and waiting for a response for each packet. Multiple packets can be sent before acknowledgment of all previous packets, but acknowledgment happens in the order that packets were sent. The sent packet looks like the following (all multibyte fields are big-endian, except for data payload, which is explained below):
[len] [crc] [cmd] [payload]
len
is a 1-byte field indicating the length of the payload; it is the "L" in TLV.crc
is a 2-byte field consisting of the CRC16 of the concatenation of thecmd
andpayload
fields. Without getting lost in the weeds of CRCs, the CRC is the same one as used in XMODEM. This page has several basic (non-table-driven) implementations for various CPUs.cmd
is a 1-byte field. It must either0
for "Abort",1
for "Load", and2
for "Jump"; it is the "T" in TLV, and modifies the payload:- "Abort" stops the transfer completely, and the sender goes back to waiting for the receiver to send the magic string.
- "Jump" finishes the transfer, and instructs the receiver's CPU to jump to the supplied address in the receiver's memory. The sender goes back to waiting for a magic string.
- "Load" loads up to 251 bytes at a specified address in the receiver's
memory. See
payload
.
payload
is the "V" in TLV, and can be up to 255 bytes in length:-
"Abort" command has no payload.
-
"Jump" command has a 4-byte payload, consisting of the address for the receiving CPU to jump to.
-
The "Load" command payload starts with a 4-byte address, and up to 251 bytes of data to write starting at the supplied address:
[payload] = "[addr] [data]"
The data part of the payload is written to the receiver memory as if the writes were done one byte at a time, in order of being received.
-
The receiver will respond to each packet with one of 4 ASCII codes (1-byte):
K
- The packet was received (acKed) successfully.C
- The packet had a CRC error.U
- The packet's CRC was fine, but thecmd
field was invalid.E
- (E
for "Error"?) The receiver timed out waiting for the entire packet. The timeout countdown is per character sent the initiallen
byte is sent. In LiteX BIOS's implementation, the timeout is 1/4 of a second for each character sent.
In the LiteX receiver can abort the transfer without notifying the sender. I have not implemented recovering from this case.
Development requires the most recent stable Rust compiler. Only developing
on Windows is supported at present. Follow the directions
here to install rustup
.
If you have the Tera Term source,
you can compile Tera Term for the GNU ABI using their
mingw.toolchain.cmake
CMake Toolchain File,
as well as do a 64-bit build. In particular, I've found the 64-bit GNU ABI
version of Tera Term to "just work" after a compiling, and develop against
that. However at present Tera Term is released only as a 32-bit application,
presumably for max compatibility. There may be other toolchain setups that work,
but the easiest setup I've found due to how Rust build scripts work
and how the windres
crate works
is to install the i686-pc-windows-msvc
host4 Rust compiler and the 32-bit
MSVC toolchain.
On 64-bit Windows, using your i686-pc-windows-msvc
-hosted Rust from within a "x86 Native Tools Command Prompt"
worked for me.
Tera Term is a C/C++ codebase, while this plugin is written in Rust. Teraterm seems
unlikely to change the data structures and functions exposed to plugins
(i.e. the plugin works for Tera Term 4 and 5), so I went ahead and generated
Rust bindings for the C/C++ plugin code that Rust understands, and include them
in the plugin crate. This prevents a user from needing the Tera Term source
just for small changes. If for some reason bindings need to be regenerated,
you need to install
bindgen-cli
and run it against wrapper.h
.
I provide a Justfile for convenience for all
of the above, so you probably also want to install just
and take a look at
the available recipes (just -l
).
I created the dialog using RisohEditor.
RisohEditor will simultaneously read and write the provided resource.h
to
extract constants for resource identifiers. Unfortunately, I haven't figured
out how to share resource.h
with Rust code well, so the constants are
duplicated as needed in the plugin.
-
If enough failed transfers happen, the plugin FSM may go out of sync with the LiteX BIOS (or any other SFL receiver). Specifically, the plugin can't tell whether the receiving side gave up. You can reset the plugin state by:
- Reopening the LiteX dialog.
- Unchecking the Active box.
- Hitting "OK".
Once you're ready to do a transfer again, make sure to open the LiteX dialog again and click the Active checkbox!
-
Related to above, the plugin should probably terminate the transfer on too many errors; it doesn't right now.
-
I don't support the JSON file for multiple boot images yet.
-
The
litex-term
implementation of SFL send supports sending and waiting for ACK for up to 10 packets at a time. Right now,teraterm-litex
waits for each packet sent to be acknowledge before sending the next one (lockstep). -
In the context of this plugin, I interchangeably call a packet a "chunk", and should probably be consistent.
This software was made possible thanks to:
- The Tera Term authors, for creating a terminal emulator optimized for serial ports that I actually like!
- RisohEditor so that I could create the LiteX dialog.
- @peddamat's article on How to create a Rust DLL.
- @enjoy-digital for his tireless work on LiteX!
- The developers of seq2gif, for a quick way to demo my plugin.
Footnotes
-
Such scenarios include:
- Creating a hand-tailored LiteX SoC
- Running Linux-on-LiteX,
- Using a SFL protocol implementation besides the LiteX BIOS.
-
As long as you're not compiling your firmware while the transfer is taking place. But why would you do that :)? ↩
-
I don't know where these strings came from; they seem to be handpicked random strings. They don't appear to be
mcookie
-based, which was my first guess:
↩$ mcookie 1c123a213bdaa17fb8c6d19ce2418e96
-
--target
alone passed tocargo
is not sufficient. You must invoke the 32-bit Rust compiler binary viarustup
. Something like:
↩rustup toolchain install stable-i686-msvc cargo +stable-i686-msvc build --release --target i686-pc-windows-msvc