Transferring files over a serial connection

Problem

Sometimes you just don’t have a stable (or even functioning network) to leverage when dealing with a piece of hardware. In all my years in Linux admin/development I’ve always worked around this problem with some sort of sneakernet, but there is another way.

Typically when bringing up new hardware one of the most basic interfaces you have is a serial console connection and I just learned recently how to transfer files over that serial console connection rather than requiring a net (tcp/ip) connection.

This can be extremely useful in cases where you’ve got something booting, but not quite fully functional. i.e. you’re up, but no network driver.

Transferring a file over serial

For connecting over serial I’ve been using tio the past few years as it’s a really good serial console client and it just so happens to support XMODEM and YMODEM protocols as well. You can use your existing serial console connection to the machine to send files this way, but the client does need a piece of software installed on it on the other side to receive the files.

The lrzsz software can do this for us. In Fedora this is provided by the lrzsz package. With this installed and a serial connection active to the remote machine I can initiate a file transfer using the rx command (run on the remote machine via the serial connection).

Let’s first connect via tio to the remote machine in question:

$ sudo tio -b 115200 /dev/ttyUSB0
Fedora CoreOS 42.20250422.dev.0
Kernel 6.13.0-0.rc4.36.0.riscv64.fc42.riscv64 on riscv64 (ttyS0)

SSH host key: SHA256:jcmPJrfPDiOKw6vJYv5FRar28WvXEr2ptP+HqYX8V0k (ECDSA)
SSH host key: SHA256:/984ZL7Usjje/nWN8xvc4plOC9d7IWo/mL+fT0zkmvE (ED25519)
SSH host key: SHA256:lSoNDXDl2Bz0s2M5xLmwzODmEqVanQDeHBYQOGVCsJw (RSA)
Ignition: ran on 2025/02/16 00:00:39 UTC (this boot)
Ignition: user-provided config was applied
Ignition: wrote ssh authorized keys file for user: core
localhost login: core
Password:

[core@localhost ~]$

Let’s check that the receiving software is in place:

[core@localhost]$ rpm -q lrzsz
lrzsz-0.12.20-74.fc42.riscv64

Now we can start the receiver. In this case what I wanted to do was transfer a new version of u-boot over to the target device in order to flash it to the SPI (so the new version of u-boot would be used for future boots:

[core@localhost]$ rx -b -c ./u-boot.itb

rx: ready to receive ./u-boot.itb
CCCCCC

The CCC are indicators that the software is waiting for the transfer to start.

On the tio side we can CTRL-t x to start a transfer. It will ask you what XMODEM strategy you want to use for the transfer. I just selected 0 for XMODEM-1K send and then chose a file to transfer:

[10:40:15.915] Please enter which X modem protocol to use:
[10:40:15.915]  (0) XMODEM-1K send
[10:40:15.915]  (1) XMODEM-CRC send
[10:40:15.915]  (2) XMODEM-CRC receive
[10:40:16.535] Send file with XMODEM-1K
[10:40:22.211] Sending file '/var/b/images/star64/usr/share/uboot/starfive_visionfive2/u-boot.itb'
[10:40:22.211] Press any key to abort transfer
................................................................||
[10:42:12.159] Done
[core@localhost ~]$

Now with the file transferred I wanted to validate the transfer using the checksum:

[core@localhost]$ sha256sum u-boot.itb
2ebb46c5317e7eb9a8b85b8de1764a58c10805d00e72bd919d741b9b7010a7b8  u-boot.itb

Unfortunately this didn’t match the checksum from my host machine:

$ sha256sum /var/b/images/star64/usr/share/uboot/starfive_visionfive2/u-boot.itb
fbb89649445f03b277eadc0953cc87b94bdbf2aba130ce6bfeb9702b87794a18  /var/b/images/star64/usr/share/uboot/starfive_visionfive2/u-boot.itb

It looks like when transferring it just pads to a 1K boundary for the file because the size on my host was 1142139:

$ ls -l /var/b/images/star64/usr/share/uboot/starfive_visionfive2/u-boot.itb
-rw-r--r--. 1 dustymabe dustymabe 1142139 Apr 26 16:58 /var/b/images/star64/usr/share/uboot/starfive_visionfive2/u-boot.itb

Where the size on the remote was 1142784:

[core@localhost footmp]$ ls -l u-boot.itb
-rw-------. 1 core core 1142784 Apr 29 14:42 u-boot.itb

Truncating the file back down to the right size gave me the correct checksum:

[core@localhost]$ truncate -s 1142139 u-boot.itb
[core@localhost]$ ls -l u-boot.itb
-rw-------. 1 core core 1142139 Apr 29 14:52 u-boot.itb
[core@localhost]$ sha256sum u-boot.itb
fbb89649445f03b277eadc0953cc87b94bdbf2aba130ce6bfeb9702b87794a18  u-boot.itb

And there the file has been transferred over a serial connection!

serial