For non-Chinese readers, in short, the source code and modification for U-Boot ZModem is here.
前陣子在公司做了一個案子,希望利用 RS422 上進行檔案傳輸,來進行系統更新。至於為什麼不用網路傳呢!?當然是客戶最大,問太多也沒用,總是有他的需求在。
U-Boot 支援現況與ZModem優點
用過 U-Boot 的資深使用者應該知道其支援利用 YModem 與 XModem 進行接收,所以在一般狀況其實用不太到 ZModem,我想這也是其一直沒有支援 ZModem 的原因吧~
根據我模糊的印象,ZModem 支援串流傳輸,而非一般的 stop-and-wait,所以效率會較高。另外,其也支援動態的 block 大小,比起 YModem 的 1024 或 256 block size,會有較好的效率。
實際測試的效果來講,在一般RS232 115200 bps 上,ZModem 與 YModem 速度大致相同,但 ZModem 開始傳輸的反應時間會快蠻多的。在高速應用 RS422 上,當用 5Mbps 傳輸時,ZModem 可達到 200KB/s,而 YModem 僅 150KB/s,算是改善了不少。
ZModem 原始碼套件
支援 ZMODEM 的原始碼,是來自 lrzsz 的這個套件,其實很早就用在 Linux 環境下了。這次因為要將其移植到 U-Boot 下,截取了其 RX 的部份,也順利完成,算是運氣還不錯,沒花太多功夫。甚至移植到 non-OS 的環境,也沒什麼問題。
U-Boot 下 ZModem 移植
ZModem 接收的程式碼,已經從 lrzsz-0.12.20 內截取下來,放在 gitlab 的「zmodem for u-boot」專案裡。裡面的 README.md 裡已有一些說明,這邊就以中文再說明一下。
新增命令
當套用這些變更後, U-Boot 就會新增一個 loadz 的命令,用法跟 loady 或 loadx 是相同的。
檔案複製
將專案下載下來後,將下列檔案複製到 U-Boot 的 common/ 目錄下:
- config.h
- error.h
- lrz.c
- zglobal.h
- zm.c
- zmodem.h
- zreadline.c
- rbsb.c
手動套用 diff 檔
有三個檔案需要使用者手動套用差異到現有檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
Index: cmd/load.c =================================================================== --- cmd/load.c (revision 323) +++ cmd/load.c (working copy) @@ -19,6 +19,7 @@ #if defined(CONFIG_CMD_LOADB) static ulong load_serial_ymodem(ulong offset, int mode); +static ulong load_serial_zmodem(ulong offset); #endif #if defined(CONFIG_CMD_LOADS) @@ -463,7 +464,14 @@ load_baudrate); addr = load_serial_ymodem(offset, xyzModem_ymodem); + } else if (strcmp(argv[0],"loadz")==0) { + printf("## Ready for binary (zmodem) download " + "to 0x%08lX at %d bps...\n", + offset, + load_baudrate); + addr = load_serial_zmodem(offset); + } else if (strcmp(argv[0],"loadx")==0) { printf("## Ready for binary (xmodem) download " "to 0x%08lX at %d bps...\n", @@ -950,6 +958,33 @@ return (getc()); return -1; } +static ulong load_serial_zmodem(ulong offset) +{ + int size; + int err; + int res; + ulong addr = 0; + + + + + size = 0; + + printf("Start to run zmodem\n"); + res = zmodem_rx(offset, &size); + printf("End zmodem error = %d\n", res); + if(res) { + printf("%s(): ZModem download has errors !!!!\n"); + return offset; + } + + flush_cache(offset, ALIGN(size, ARCH_DMA_MINALIGN)); + + printf("## Total Size = 0x%08x = %d Bytes\n", size, size); + env_set_hex("filesize", size); + + return offset; +} static ulong load_serial_ymodem(ulong offset, int mode) { int size; @@ -1078,5 +1113,12 @@ " - load binary file over serial line" " with offset 'off' and baudrate 'baud'" ); +U_BOOT_CMD( + loadz, 3, 0, do_load_serial_bin, + "load binary file over serial line (zmodem mode)", + "[ off ] [ baud ]\n" + " - load binary file over serial line" + " with offset 'off' and baudrate 'baud'" +); #endif /* CONFIG_CMD_LOADB */ |
cmd/load.c, 加入 ‘loadz’ 命令的辨識,並呼叫 load_serial_zmodem()。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Index: common/Makefile =================================================================== --- common/Makefile (revision 323) +++ common/Makefile (working copy) @@ -130,7 +130,7 @@ obj-$(CONFIG_$(SPL_TPL_)LOG) += log.o obj-$(CONFIG_$(SPL_TPL_)LOG_CONSOLE) += log_console.o obj-y += s_record.o -obj-$(CONFIG_CMD_LOADB) += xyzModem.o +obj-$(CONFIG_CMD_LOADB) += xyzModem.o lrz.o rbsb.o zm.o zreadline.o obj-$(CONFIG_$(SPL_TPL_)YMODEM_SUPPORT) += xyzModem.o obj-$(CONFIG_AVB_VERIFY) += avb_verify.o |
common/Makefile, 將新檔案加入 Makefile 的 compile list。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
Index: common/xyzModem.c =================================================================== --- common/xyzModem.c (revision 323) +++ common/xyzModem.c (working copy) @@ -51,7 +51,8 @@ unsigned long file_length, read_length; } xyz; -#define xyzModem_CHAR_TIMEOUT 2000 /* 2 seconds */ +#define _xyzModem_CHAR_TIMEOUT 2000 /* 2 seconds */ +int xyzModem_CHAR_TIMEOUT = _xyzModem_CHAR_TIMEOUT; #define xyzModem_MAX_RETRIES 20 #define xyzModem_MAX_RETRIES_WITH_CRC 10 #define xyzModem_CAN_COUNT 3 /* Wait for 3 CAN before quitting */ @@ -58,7 +59,7 @@ typedef int cyg_int32; -static int +int CYGACC_COMM_IF_GETC_TIMEOUT (char chan, char *c) { @@ -76,7 +77,7 @@ return 0; } -static void +void CYGACC_COMM_IF_PUTC (char x, char y) { putc (y); |
將 「static int CYGACC_COMM_IF_GETC_TIMEOUT (char chan, char *c)」與「static void CYGACC_COMM_IF_PUTC (char x, char y)」從 static,改成非 static。
開始測試
當完成上面的修改後,應該就可以在 U-Boot 上透過 ZModem 來接收檔案。可以使用免費的 Teraterm 來進行測試,或以商業的 SecureCRT。以測試結果來講,SecureCRT 會較快,在一些更快的 Serial Port (如 USB Gadget UART) ,SecureCRT 可達到 3MB/s 而 Teraterm 仍只有數百KB。
移植到其它平台
若需要移植到其它非 U-Boot 的平台上,ex.沒有 OS 的 standalone application (通常是個大 while loop),會需要修改一些跟 UART 傳送接收有關的 function,即單字元的接收與發送之類的。這些修改,都放在 lrz.c 裡面。
void sendline(int c)
將一個字元發送到 uart 去
int read_data(int tout_in_100ms, char *buf, int size)
讀取UART的輸入字元,tout_in_100ms 是沒資料時的 timeout 時間,以100ms為單位。buf 是存放的 buffer pointer,而 size 是 buffer 的大小。返回值代表讀取到的資料個數,返回0則是代表 timeout。目前的程式碼是一次只會讀入一個字元,若不同平台讀到的資料較多,也可以在buffer內一次放入較多資料。
void send_data(int fd, char *buf, int size)
送出一串字元。不修改的話,就是以 sendline() 來組合實現。
void flushmo()
目前沒用過。猜測是有的裝置會有 TX fifo ,呼叫此 function 來確實將資料推送出去。
double timing (int reset, time_t *nowp)
用來計算經過秒數的 function。計算 reset=1 與 reset=0 中間經過的秒數,以 double 為單位,試過返回整數秒也是可以的。
結語
ZModem 這些古老的 protocol 在現經真是很少碰到了,所以在 U-Boot 上也遲遲沒有實現,依稀記得在 2009 年就有人在問這個問題了。這麼冷門的知識,希望能幫到看文章的你。
附件
怕以後 gitlab GG,把 gitlab 的檔案放在這裡。