/************************************************************************** * * Copyright (c) 2013 Alcatel-Lucent * * Alcatel Lucent licenses this file to You under the Apache License, * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. A copy of the License is contained the * file LICENSE at the top level of this repository. * You may also obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ************************************************************************** * * xmodem.c: * * Command to upload or download via XMODEM protocol. Xmodem is quite * limited, but adequate for simple file up/down load. * This also supports XMODEM-1K and YMODEM. YMODEM is an extension to XMODEM * that uses a 1K packet size, CRC and the first packet (seqno=0) contains * information about the file being downloaded (in partiuclar, the name). * YMODEM also supports BATCH downloads (multiple files downloaded in one * transaction). This code supports incoming BATCH downloads (not tested * because I can't find any terminal emulators that do it), but not for * uploads. * Minicom notes: * Minicom is the terminal emulation program that comes with Linux. * For some reason I had to make some adjustments to the xmodem.c * to accommodate Minicom... * - Minicom sends an initial 0x1a (EOF). Not sure why, so I just absorb * that now. * - Minicom doesn't like a fast nak-resend rate, so each additional * 'd' option on the xmodem command line now causes the delay * between outgoing NAKs (used to start up the protocol in Xdown()) * to double. As a result, when running with minicom, a file download * may need "xmodem -ddd". Not exactly sure why this is necessary but * here's my hunch... * To do an xmodem transfer to the target running uMon, the "xmodem -d" * command is issued on the uMon command line. This causes uMon to start * generating periodic NAKs (part of the xmodem startup handshake). * Then via CTRL-A Z, the minicom side of the transaction starts up. * Apparently, minicom doesn't flush the input buffer when the xmodem * transfer is started on its side. As a result there may be several * NAKs buffered up on the TTY. The end result is that if too many * NAKs have been queued up in the TTY's buffer, Minicom chokes on * them at startup. * Adding the ability to slow down the NAK send rate keeps uMon from * sending too many NAKs prior to Minicom actually starting up its * side of the protocol; hence, Minicom doesn't choke and the transfer * works ok. * * By default, this code will disable the ethernet interface * during the xmodem transaction. This is done because in most * cases the monitor runs with no flow control; and getchar polls * the ethernet interface. If the poll results in an incoming * packet, then it may cause the serial port to lose characters. * If this is not desireable (or if the uart has a large input fifo), * then this can be changed with the DONT_DISABLE_ENET_IN_XMODEM * #define set up in config.h * * Original author: Ed Sutter (ed.sutter@alcatel-lucent.com) * */ #include "config.h" #include "genlib.h" #include "stddefs.h" #include "flash.h" #include "tfs.h" #include "tfsprivate.h" #include "cli.h" #include "ether.h" #include "timer.h" #if INCLUDE_XMODEM #define PUTCHAR putchar /* struct xinfo: * Used to contain information pertaining to the current transaction. * The structure is built by the command Xmodem, then passed to the other * support functions (Xup, Xdown, etc..) for reference and update. */ struct xinfo { uchar sno; /* Sequence number. */ uchar pad; /* Unused, padding. */ int xfertot; /* Running total of transfer. */ int pktlen; /* Length of packet (128 or 1024). */ int pktcnt; /* Running tally of number of packets processed. */ int filcnt; /* Number of files transferred by ymodem. */ long size; /* Size of upload. */ ulong flags; /* Storage for various runtime flags. */ ulong base; /* Starting address for data transfer. */ ulong dataddr; /* Running address for data transfer. */ int errcnt; /* Keep track of errors (used in verify mode). */ int nakresend; /* Time between each NAK sent at Xdown startup. */ char *firsterrat; /* Pointer to location of error detected when */ /* transfer is in verify mode. */ char fname[TFSNAMESIZE]; }; /* Runtime flags: */ #define USECRC (1<<0) #define VERIFY (1<<1) #define YMODEM (1<<2) /* Current xmodem operation: */ #define XNULL 0 #define XUP 1 #define XDOWN 2 /* X/Ymodem protocol: */ #define SOH 0x01 #define STX 0x02 #define EOT 0x04 #define ACK 0x06 #define NAK 0x15 #define CAN 0x18 #define EOF 0x1a #define ESC 0x1b #define PKTLEN_128 128 #define PKTLEN_1K 1024 #define BYTE_TIMEOUT 1000 static int Xup(struct xinfo *); static int Xdown(struct xinfo *); static int getPacket(uchar *,struct xinfo *); static int putPacket(uchar *,struct xinfo *); char *XmodemHelp[] = { "Xmodem file transfer", "-[a:BcdF:f:i:ks:uvy]", #if INCLUDE_VERBOSEHELP "Options:", " -a{##} address (overrides default of APPRAMBASE)", #if INCLUDE_FLASH " -B boot sector reload", #endif " -c use crc (default = checksum)", " -d download", #if INCLUDE_TFS " -F{name} filename", " -f{flags} file flags (see tfs)", " -i{info} file info (see tfs)", #endif " -k force packet length to 1K", " -s{##} size (overrides computed size)", " -u upload", " -v verify only", #if INCLUDE_TFS " -y use Ymodem extensions", #endif "Notes:", " * Either -d or -u must be specified (-B implies -d).", " * Each additional 'd' option cuts the nak-resend rate in half.", " * XMODEM forces a 128-byte modulo on file size. The -s option", " can be used to override this when transferring a file to TFS.", " * File upload requires no address or size (size will be mod 128).", " * When using -B, it should be the ONLY command line option,", " it's purpose is to reprogram the boot sector, so be careful!", #endif (char *)0, }; int Xmodem(int argc,char *argv[]) { int opt, xop; struct xinfo xi; #if INCLUDE_TFS char *info = (char *)0; char *flags = (char *)0; TFILE *tfp; #endif #if INCLUDE_FLASH int newboot = 0; #endif #if INCLUDE_ETHERNET int eon; #endif xop = XNULL; xi.fname[0] = 0; xi.size = 0; xi.flags = 0; xi.filcnt = 0; xi.nakresend = 2; xi.pktlen = PKTLEN_128; xi.base = xi.dataddr = getAppRamStart(); while((opt=getopt(argc,argv,"a:BcdF:f:i:ks:uvy")) != -1) { switch(opt) { case 'a': xi.dataddr = xi.base = strtoul(optarg,(char **)0,0); break; case 'B': xop = XDOWN; #if INCLUDE_FLASH newboot = 1; #endif break; case 'c': xi.flags |= USECRC; break; case 'd': xi.nakresend *= 2; /* -ddd increases Xdown() resend delay */ xop = XDOWN; break; #if INCLUDE_TFS case 'F': strncpy(xi.fname,optarg,TFSNAMESIZE); break; case 'f': flags = optarg; break; case 'i': info = optarg; break; #endif case 'k': xi.pktlen = PKTLEN_1K; break; case 's': xi.size = strtol(optarg,(char **)0,0); break; case 'u': xop = XUP; break; case 'v': xi.flags |= VERIFY; break; #if INCLUDE_TFS case 'y': xi.flags |= (YMODEM | USECRC); xi.pktlen = PKTLEN_1K; break; #endif default: return(CMD_PARAM_ERROR); } } /* There should be no arguments after the option list. */ if(argc != optind) { return(CMD_PARAM_ERROR); } if(xop == XUP) { if((xi.flags & YMODEM) && !(xi.fname[0])) { printf("Ymodem upload needs filename\n"); } else { if(xi.fname[0]) { /* Causes -a and -s options to be ignored. */ #if INCLUDE_TFS tfp = tfsstat(xi.fname); if(!tfp) { printf("%s: file not found\n",xi.fname); return(CMD_FAILURE); } xi.base = xi.dataddr = (ulong)TFS_BASE(tfp); xi.size = TFS_SIZE(tfp); #endif } #ifdef DONT_DISABLE_ENET_IN_XMODEM /* See note at top of file */ Xup(&xi); #else #if INCLUDE_ETHERNET eon = DisableEthernet(); #endif Xup(&xi); #if INCLUDE_ETHERNET if(eon) { EthernetStartup(0,0); } #endif #endif } } else if(xop == XDOWN) { long tmpsize; if((xi.flags & YMODEM) && (xi.fname[0])) printf("Ymodem download gets name from protocol, '%s' ignored\n", xi.fname); #ifdef DONT_DISABLE_ENET_IN_XMODEM /* See note at top of file */ tmpsize = (long)Xdown(&xi); #else #if INCLUDE_ETHERNET eon = DisableEthernet(); #endif tmpsize = (long)Xdown(&xi); #if INCLUDE_ETHERNET if(eon) { EthernetStartup(0,0); } #endif #endif if(tmpsize == -1) { return(CMD_FAILURE); } if((!xi.size) || (tmpsize < 0)) { xi.size = tmpsize; } #if INCLUDE_TFS if((xi.fname[0]) && (xi.size > 0)) { int err; printf("Writing to file '%s'...\n",xi.fname); err = tfsadd(xi.fname,info,flags,(uchar *)xi.base,xi.size); if(err != TFS_OKAY) { printf("%s: %s\n",xi.fname,(char *)tfsctrl(TFS_ERRMSG,err,0)); return(CMD_FAILURE); } } #if INCLUDE_FLASH else #endif #endif #if INCLUDE_FLASH if((newboot) && (xi.size > 0)) { extern int FlashProtectWindow; int rc; char *bb; ulong bootbase; bb = getenv("BOOTROMBASE"); if(bb) { bootbase = strtoul(bb,0,0); } else { bootbase = BOOTROM_BASE; } #ifdef TO_FLASH_ADDR /* The address the program is linked to is not necessarily the * physical address where flash operations can be performed on. * A typical use could be that the program is linked to run in * a cacheable region but writing to the flash can only be done * in an uncached region. * "config.h" is a good place to define the TO_FLASH_ADDR macro. */ bootbase = (ulong)TO_FLASH_ADDR(bootbase); #endif printf("Reprogramming boot @ 0x%lx from 0x%lx, %ld bytes.\n", bootbase,xi.base,xi.size); if(askuser("OK?")) { int first, last; /* If sector(s) need to be unlocked, do that now... */ if(addrtosector((uchar *)bootbase,&first,0,0) == -1) { return(CMD_FAILURE); } if(addrtosector((uchar *)bootbase+xi.size-1,&last,0,0) == -1) { return(CMD_FAILURE); } if(flashlock(first,FLASH_LOCKABLE) != -1) { FlashProtectWindow = 1; while(first < last) { flashlock(first++,FLASH_UNLOCK); } } FlashProtectWindow = 1; rc = flashewrite((uchar *)bootbase,(uchar *)xi.base,xi.size); if(rc == -1) { printf("failed\n"); return(CMD_FAILURE); } } } #endif } else { return(CMD_PARAM_ERROR); } shell_sprintf("XMODEMGET","%ld",xi.size); return(CMD_SUCCESS); } /* xgetchar(): * Wrap getchar with a timer, so that after 5 seconds of waiting * we giveup... */ int xgetchar(char *cp, int lno) { struct elapsed_tmr tmr; startElapsedTimer(&tmr,5000); while(!msecElapsed(&tmr) && !gotachar()); if(!gotachar()) { Mtrace("Xgetchar tmt %d",lno); return(-1); } *cp = getchar(); return(0); } /* putPacket(): * Used by Xup to send packets. */ static int putPacket(uchar *tmppkt, struct xinfo *xip) { char c; int i; ushort chksm; chksm = 0; if(xip->pktlen == PKTLEN_128) { PUTCHAR(SOH); } else { PUTCHAR(STX); } PUTCHAR((char)(xip->sno)); PUTCHAR((char)~(xip->sno)); if(xip->flags & USECRC) { for(i=0; ipktlen; i++) { PUTCHAR((char)*tmppkt); chksm = (chksm<<8)^xcrc16tab[(chksm>>8)^*tmppkt++]; } /* An "endian independent way to extract the CRC bytes. */ PUTCHAR((char)(chksm >> 8)); PUTCHAR((char)chksm); } else { for(i=0; ipktlen; i++) { PUTCHAR((char)*tmppkt); chksm = ((chksm+*tmppkt++)&0xff); } PUTCHAR((char)(chksm&0x00ff)); } if(xgetchar(&c,__LINE__) == -1) { /* Wait for ack */ return(-1); } /* If pktcnt == -1, then this is the first packet sent by * YMODEM (filename) and we must wait for one additional * character in the response. */ if(xip->pktcnt == -1) { if(xgetchar(&c,__LINE__) == -1) { return(-1); } } return((int)c); } /* getPacket(): * Used by Xdown to retrieve packets. */ static int getPacket(uchar *tmppkt, struct xinfo *xip) { char *pkt; int i,rcvd; uchar seq[2]; if((rcvd = getbytes_t((char *)seq,2,BYTE_TIMEOUT)) != 2) { PUTCHAR(NAK); Mtrace("RCVD %d != 2",rcvd); return(0); } if(xip->flags & VERIFY) { rcvd = getbytes_t((char *)tmppkt,xip->pktlen,BYTE_TIMEOUT); if(rcvd != xip->pktlen) { PUTCHAR(NAK); Mtrace("RCVD %d != %d",rcvd,xip->pktlen); return(0); } for(i=0; ipktlen; i++) { if(tmppkt[i] != ((char *)xip->dataddr)[i]) { if(xip->errcnt++ == 0) { xip->firsterrat = (char *)(xip->dataddr+i); } } } pkt = (char *)tmppkt; } else { rcvd = getbytes_t((char *)xip->dataddr,xip->pktlen,BYTE_TIMEOUT); if(rcvd != xip->pktlen) { PUTCHAR(NAK); Mtrace("RCVD %d != %d",rcvd,xip->pktlen); return(0); } pkt = (char *)xip->dataddr; } if(xip->flags & USECRC) { char c; ushort crc, xcrc; /* An "endian independent way to combine the CRC bytes. */ if(xgetchar(&c,__LINE__) == -1) { return(0); } crc = (ushort)c; crc <<= 8; if(xgetchar(&c,__LINE__) == -1) { return(0); } crc += (ushort)c; xcrc = xcrc16((uchar *)pkt,(ulong)(xip->pktlen)); if(crc != xcrc) { PUTCHAR(NAK); Mtrace("CRC %04x != %04x",crc,xcrc); return(0); } } else { uchar csum, xcsum; if(xgetchar((char *)&xcsum,__LINE__) == -1) { return(0); } csum = 0; for(i=0; ipktlen; i++) { csum += *pkt++; } if(csum != xcsum) { PUTCHAR(NAK); Mtrace("CSUM %02x != %02x (%d)",csum,xcsum,xip->pktlen); return(0); } Mtrace("CSUM %02x (%d)",csum,xip->pktlen); } /* Test the sequence number compliment... */ if((uchar)seq[0] != (uchar)~seq[1]) { PUTCHAR(NAK); Mtrace("SNOCMP %02x != %02x",(uchar)seq[0],(uchar)~(seq[1])); return(0); } /* Verify that the incoming sequence number is the expected value... */ if((uchar)seq[0] != xip->sno) { /* If the incoming sequence number is one less than the expected * sequence number, then we assume that the sender did not recieve * our previous ACK, and they are resending the previously received * packet. In that case, we send ACK and don't process the * incoming packet... */ if((uchar)seq[0] == xip->sno-1) { Mtrace("R_ACK"); PUTCHAR(ACK); return(0); } /* Otherwise, something's messed up... */ PUTCHAR(CAN); Mtrace("SNO: %02x != %02x",seq[0],xip->sno); return(-1); } /* First packet of YMODEM contains information about the transfer: * FILENAME SP FILESIZE SP MOD_DATE SP FILEMODE SP FILE_SNO * Only the FILENAME is required and if others are present, then none * can be skipped. */ if((xip->flags & YMODEM) && (xip->pktcnt == 0)) { char *slash, *space, *fname; slash = strrchr((char *)(xip->dataddr),'/'); space = strchr((char *)(xip->dataddr),' '); if(slash) { fname = slash+1; } else { fname = (char *)(xip->dataddr); } Mtrace("",fname); if(space) { *space = 0; xip->size = atoi(space+1); } strcpy(xip->fname,fname); if(fname[0]) { xip->filcnt++; } } else { xip->dataddr += xip->pktlen; } xip->sno++; xip->pktcnt++; xip->xfertot += xip->pktlen; Mtrace("ACK"); PUTCHAR(ACK); if(xip->flags & YMODEM) { if(xip->fname[0] == 0) { printf("\nRcvd %d file%c\n", xip->filcnt,xip->filcnt > 1 ? 's' : ' '); return(1); } } return(0); } /* Xup(): * Called when a transfer from target to host is being made (considered * an upload). */ static int Xup(struct xinfo *xip) { uchar c, buf[PKTLEN_128]; int tmp, done, pktlen; Mtrace("Xup starting"); if(xip->size & 0x7f) { xip->size += 128; xip->size &= 0xffffff80L; } printf("Upload %ld bytes from 0x%lx\n",xip->size,(ulong)xip->base); /* Startup synchronization... */ /* Wait to receive a NAK or 'C' from receiver. */ done = 0; while(!done) { if(xgetchar((char *)&c,__LINE__) == -1) { return(0); } switch(c) { case NAK: done = 1; Mtrace("CSM"); break; case 'C': xip->flags |= USECRC; done = 1; Mtrace("CRC"); break; case 'q': /* ELS addition, not part of XMODEM spec. */ return(0); default: break; } } rawon(); if(xip->flags & YMODEM) { Mtrace("SNO_0"); xip->sno = 0; xip->pktcnt = -1; memset((char *)buf,0,PKTLEN_128); sprintf((char *)buf,"%s",xip->fname); pktlen = xip->pktlen; xip->pktlen = PKTLEN_128; if(putPacket(buf,xip) == -1) { return(0); } xip->pktlen = pktlen; } done = 0; xip->sno = 1; xip->pktcnt = 0; while(!done) { if((tmp = putPacket((uchar *)(xip->dataddr),xip)) == -1) { return(0); } c = (uchar)tmp; switch(c) { case ACK: xip->sno++; xip->pktcnt++; xip->size -= xip->pktlen; xip->dataddr += xip->pktlen; Mtrace("A"); break; case NAK: Mtrace("N"); break; case CAN: done = -1; Mtrace("C"); break; case EOT: done = -1; Mtrace("E"); break; default: done = -1; Mtrace("<%02x>",c); break; } if(xip->size <= 0) { char tmp; PUTCHAR(EOT); if(xgetchar(&tmp,__LINE__) == -1) { /* Flush the ACK */ return(0); } break; } Mtrace("!"); } Mtrace("Xup_almost"); if((done != -1) && (xip->flags & YMODEM)) { xip->sno = 0; memset((char *)buf,0,PKTLEN_128); pktlen = xip->pktlen; xip->pktlen = PKTLEN_128; if(putPacket(buf,xip) == -1) { return(0); } xip->pktlen = pktlen; } Mtrace("Xup_done."); rawoff(); return(0); } /* Xdown(): * Called when a transfer from host to target is being made (considered * an download). * Note that if we don't have INCLUDE_MALLOC set (in config.h), then * we allocate a 128-byte static buffer and only support the 128-byte * packet size here. */ static int Xdown(struct xinfo *xip) { struct elapsed_tmr tmr; char c, *tmppkt; int i, done; #if !INCLUDE_MALLOC static char pkt[PKTLEN_128]; tmppkt = pkt; #else tmppkt = malloc(PKTLEN_1K); if(!tmppkt) { Mtrace("malloc failed"); return(-1); } #endif rawon(); nextfile: if(xip->flags & YMODEM) { xip->sno = 0x00; } else { xip->sno = 0x01; } xip->pktcnt = 0; xip->errcnt = 0; xip->xfertot = 0; xip->firsterrat = 0; /* Startup synchronization... */ /* Continuously send NAK or 'C' until sender responds. */ restart: Mtrace("Xdown"); for(i=0; i<32; i++) { if(xip->flags & USECRC) { PUTCHAR('C'); } else { PUTCHAR(NAK); } startElapsedTimer(&tmr,xip->nakresend * 1000); while(!msecElapsed(&tmr) && !gotachar()); if(gotachar()) { break; } } if(i == 32) { Mtrace("Giveup @ %d",__LINE__); return(-1); } done = 0; Mtrace("Got response"); while(done == 0) { if(xgetchar(&c,__LINE__) == -1) { return(-1); } switch(c) { case SOH: /* 128-byte incoming packet */ Mtrace("O"); xip->pktlen = 128; done = getPacket((uchar *)tmppkt,xip); if(done < 0) { Mtrace("GP_%d",done); } if(!done && (xip->pktcnt == 1) && (xip->flags & YMODEM)) { goto restart; } break; #if INCLUDE_MALLOC case STX: /* 1024-byte incoming packet */ Mtrace("T"); xip->pktlen = 1024; done = getPacket((uchar *)tmppkt,xip); if(done < 0) { Mtrace("GP_%d",done); } if(!done && (xip->pktcnt == 1) && (xip->flags & YMODEM)) { goto restart; } break; #endif case CAN: Mtrace("C"); done = -1; break; case EOT: Mtrace("E"); PUTCHAR(ACK); if(xip->flags & YMODEM) { #if INCLUDE_TFS if(!xip->size) { xip->size = xip->pktcnt * xip->pktlen; } if(xip->fname[0]) { tfsadd(xip->fname,0,0,(uchar *)xip->base,xip->size); } xip->dataddr = xip->base; #endif goto nextfile; } else { done = xip->xfertot; rawoff(); printf("\nRcvd %d pkt%c (%d bytes)\n",xip->pktcnt, xip->pktcnt > 1 ? 's' : ' ',xip->xfertot); /* If the transfer is complete and no file add is to * be done, then we flush d-cache and invalidate * i-cache across the memory space that was just * copied to. This is necessary in case the * binary data that was just transferred is code. */ flushDcache((char *)xip->base,xip->xfertot); invalidateIcache((char *)xip->base,xip->xfertot); } break; case ESC: /* User-invoked abort */ Mtrace("X"); done = -1; break; case EOF: /* 0x1a sent by MiniCom, just ignore it. */ break; default: Mtrace("<%02x>",c); done = -1; break; } Mtrace("!"); } rawoff(); if(xip->flags & VERIFY) { if(xip->errcnt) printf("%d errors, first at 0x%lx\n", xip->errcnt,(ulong)(xip->firsterrat)); else { printf("verification passed\n"); } } free(tmppkt); return(done); } #endif