How to transfer files to a Linux embedded system over serial

Imagine a scenario where you have a device running an embedded Linux system, but you flashed a bad firmware which doesn’t let you to have access to Ethernet/WiFi on this device. You only have access to it is using serial cable.

First thing you think is to use U-Boot transfer support over serial, but unfortunately you don’t have this option:

U-Boot 1.1.4 (Feb 27 2009 - 12:06:20)

AP94 (ar7100) U-boot 0.0.12
(sic)
In:    serial
Out:   serial
Err:   serial
Net:   eth0, eth1
### main_loop entered: bootdelay=1

### main_loop: bootcmd="bootm 0xbf050000"
Hit any key to stop autoboot:  0 
### main_loop: got key string, abort boot
### main_loop: no define CONFIG_AUTOBOOT_KEYED
### main_loop: CFG_HUSH_PARSER
### main_loop: parse_string_outer
## Booting image at bf050000 ...
checksum:743d0796
   Image Name:   MIPS OpenWrt Linux-2.6.39.4
   Image Type:   MIPS Linux Kernel Image (lzma  compressed)
   Data Size:    873389 Bytes = 852.9 kB
   Load Address: 80060000
   Entry Point:  80060000
   Verifying Checksum ... OK
   LZMA Umcompressing Kernel Image ...    Image loaded from 80060000-802e82d8

As you can see it is not entering on U-Boot prompt because CONFIG_AUTOBOOT_KEYED is not defined.

Then we need to find another alternative.

Unfortunately we are not lucky, the linux system on this router doesn’t have “lrz” program installed.

But all is not lost, I have an idea!!!

The “lrz” program is relatively small, about 60KB, then I can convert it to text and past on Linux terminal running inside de box (using echo and pasting text). Unfortunately I cannot use base64 program, because it also is not install on this Linux system.

The remaining option is using hexdump (xxd) to convert the binary on text bytes and the recover it, as I suggested some times ago (link in Portuguese).

So, let to do it, first thing to do is download the package containing the lrz program (lrzsz_0.12.20-1_ar71xx.ipk).

Hmm, this is 59792 bytes long, no problem I hope.

Now let to convert it from binary to text:

$ xxd -g1 lrz > output.txt

We need to remove the “00xxxxx: ” from beginning of each line and the 16 chars representation at end of each line:

$ sed -i 's/^\(.\)\{9\}//g' output.txt
$ sed -i 's/\(.\)\{16\}$//g' output.txt

Now let to convert this text back to binary to confirm it is working:

$ for i in $(cat output.txt) ; do printf "\x$i" ; done > mylrz

Let to check if they are have same binary sequence:

$ md5sum mylrz 
08334803ce751a1f021a4de0c9c0e849  mylrz
$ md5sum lrz 
08334803ce751a1f021a4de0c9c0e849  lrz

Now just let to copy and past this content of text to linux box terminal.

I suggest you to use picocom instead minicom to do it:

$ picocom -b 115200 -l -r /dev/ttyUSB0

I started trying to send line by line this way:

root@OpenWrt:/# echo "7f 45 4c 46 01 02 01 00 01 00 00 00 00 00 00 00" >> mylrz.txt

In few seconds it became a very boring task and very prone to fail.

Then I decide to send commands directly to terminal over serial using shell script.

Leave picocom…

After some tests I noticed it is not possible to send many characters to terminal (over /dev/ttyUSB0), no more 2 bytes by time.

Then the command became this long line:

$ j=0; echo -n -e "ec" > /dev/ttyUSB0; echo -n -e "ho" > /dev/ttyUSB0; echo -n -e " \"" > /dev/ttyUSB0; for i in $(cat output.txt); do j=$(($j+1)); echo -n -e "$i" > /dev/ttyUSB0; echo -n -e " " > /dev/ttyUSB0; if [ $(expr $j % 16) -eq 0 ]; then echo -n -e "\" " > /dev/ttyUSB0; echo -n -e ">>" > /dev/ttyUSB0; echo -n -e "lr" > /dev/ttyUSB0; echo -n -e "z" > /dev/ttyUSB0; echo " " > /dev/ttyUSB0; echo -n -e "ec" > /dev/ttyUSB0; echo -n -e "ho" > /dev/ttyUSB0; echo -n -e " \"" > /dev/ttyUSB0; fi; done; echo -n -e "ec" > /dev/ttyUSB0; echo -n -e "ho" > /dev/ttyUSB0; echo "\"" > /dev/ttyUSB0

Verify if file lrz file was created:

root@OpenWrt:/# ls -l lrz
-rw-r--r--    1 root     root        183113 Jan  1 01:19 lrz

Rename it to lrz.txt:

root@OpenWrt:/# mv lrz lrz.txt

Convert the text file to binary:

root@OpenWrt:/# for i in $(cat lrz.txt) ; do printf "\x$i" ; done > lrz
root@OpenWrt:/# md5sum lrz
08334803ce751a1f021a4de0c9c0e849  lrz

Give it permission to execute and move to /usr/bin:

root@OpenWrt:/# chmod 555 lrz
root@OpenWrt:/# mv lrz /usr/bin/

Execute the lrz command to receive file:

root@OpenWrt:/# lrz -Z

Leave picocom program (Ctrl+A+X)

In you computer execute this command to transfer firmware to your router:

$ sz --zmodem /tmp/openwrt-ar71xx-dir825.bin > /dev/ttyUSB0 < /dev/ttyUSB0

Check with md5sum to verify if it is correct:

root@OpenWrt:/tmp# md5sum openwrt-ar71xx-dir825.bin 
90e938ed4a5aa825495c2b0b099655e7  openwrt-ar71xx-dir825.bin

alan@aureo:~/Desktop/serial$ md5sum /tmp/openwrt-ar71xx-dir825.bin 
90e938ed4a5aa825495c2b0b099655e7  /tmp/openwrt-ar71xx-dir825.bin

Now just update your system using this new firmware:

root@OpenWrt:/# sysupgrade /tmp/openwrt-ar71xx-dir825.bin

14 thoughts on “How to transfer files to a Linux embedded system over serial

  1. WOW AMAZING!!! I never thought about that!!! Thanks for your tips Now, I can fix my router in a no network state!!

  2. Hi! Great manual!

    I tried it out but when trying to run ‘lrz -Z’ on the target, I get ‘segmentation fault’.

    Could it be related to the fact that you didn’t mention the need for ‘stty’ command?
    I mean, before the output.txt transfer loop, there is a need to set the baudrate to 115200.
    Something like that:
    $ stty -F /dev/ttyUSB0 115200

    Thanks in advance,
    Yechiel

    1. Hi Yechiel,
      Thank you very much for your comment. It is nice to know it was useful for you!
      Are you sure the “segmentation fault” was caused by wrong baudrate? When you executed stty the issue disappeared? BR, Alan

  3. oi Acassis, gostei muito do artigo, eu estou com um problema não consigo fazer a transferência do arquivo texto em hexadecimal ocorre erro trava, eu não uso o linux tem algum programa que faça isso no windows ? se possível fazer um vídeo ou recomendar um, grato

  4. Oi Caique, provavelmente o erro ocorre porque a sequencia de comandos foi digitada de forma errada. Não existe um programa que faça isso automaticamente, nem no Linux e nem no Windows. Você vai ter que entrar na água pra aprender a nadar. Abraços!

  5. Oi Caique,
    mesmo com o limite de caracteres se você continuar digitando o comando continuará na linha de baixo. Ou então use um terminal em modo gráfico e redimensione a tela para caber mais caracteres. Abs.

  6. Know this article is from a long time ago but wanted to say that you helped me out a ton today. Flashed an OpenWRT update that killed the LAN/WAN interfaces a while back–oops. Then had to solder pins to the JTAG interface to get into serial and transfer a new image to flash. Even after all these years your article is still the most helpful and easy to follow on getting files copied over without initially having zmodem etc. Hope you’re still continuing to help people out.

    1. Hi Julia, nice to hear it! I hope you got your router working fine with OpenWRT again. I also document these things because I know it could help someone. BR, Alan

  7. Hello, Thanks for the nice trick, I had used this steps to copy lrz into my embedded terminal.
    To receive the file inside the target, i had used ‘cat > filetobereceived.txt’ inside target and sent the file from host machine using teraterm ‘Send file ‘ option.

    1. Hi Yuva Raj, very nice! I’m glad to know these tips helped you! I think using minicom ascii transfer also should work on Linux. I will try it later! Thanks for your comment.

  8. Thank you Alan for helping me rescue my OpenWrt router!
    Your ASCII sending command didn’t work for me. It seems to assume that the last line is a multiple of 16 bytes?

    But I found [1] you can also use “ascii-xfr” (part of the minicom package) for this purpose.
    On the receiving side “cat > lrz.txt” would have to be used. But on my router I had the issue that something [2] was making the Ctrl+C (needed after transfer completion) being ignored. So I used a little timer script to stop the receiving program (didn’t work with cat due to the >, so used tee).

    To summarize getting lrz onto the router:

    – lrz => output.txt conversion like shown by you
    – $ picocom -b 115200 –send-cmd “ascii-xfr -snv” /dev/ttyUSB0
    – router# cd /tmp
    – router# $(sleep 60 && killall tee) &
    – router# tee lrz.txt
    – Ctrl+A Ctrl+S output.txt
    – output.txt => lrz conversion like shown by you

    And finally I also used picocom’s send command feature for transfering the firmware:

    – $ picocom -b 115200 –send-cmd “sz –zmodem -vv” /dev/ttyUSB
    – router# ./lrz -Z
    – Ctrl+A Ctrl+S firmware.bin
    – sysupgrade 🙂
    (No need for the sleep+killall trick here, because lrz ends when the transfer ends.)

    Hope this helps someone.

    [1] https://unix.stackexchange.com/a/296752
    [2] http://lists.busybox.net/pipermail/busybox/2003-April/042310.html

    1. You are welcome Mathias! It nice to know you fixed your router. In fact I miss USB Firmware Update (DFU) for these router. It could be very useful, but also very dangerous if someone got physical access to your modem and want to modify it.

Leave a comment