/*! \file loader.c \brief BrickOS task downloading \author Markus L. Noga */ /* * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The Original Code is legOS code, released October 2, 1999. * * The Initial Developer of the Original Code is Markus L. Noga. * Portions created by Markus L. Noga are Copyright (C) 1999 * Markus L. Noga. All Rights Reserved. * * Contributor(s): everyone discussing LNP at LUGNET */ /* 2000.05.01 * * Modifications to the original loader.c file for LegOS 0.2.2 include: * * Paolo Masetti's patches to eliminate "Invalid Argument" error and * to get rid of several compiler warnings: * * http://www.lugnet.com/robotics/rcx/legos/?n=619 * * Markus L. Noga's solution to Cygwin's failure to define the O_ASYNC symbol: * * http://www.lugnet.com/robotics/rcx/legos/?n=439 * * Paolo Masetti's adaptation for definitive porting to Win32. No more errors in * serial communication and conditional compile for Winnt (cygnwin). * Several addition to dll option to better support user. Execute dll without * arguments to get help. * */ /* 2002.04.01 * * Modifications to the original loader.c file in LegOS 0.2.4 include: * * Hary D. Mahesan's update to support USB IR firmware downloading * using RCX 2.0's USB tower under WIN32 on Cygwin. * * * * CVS inclusion, revision and modification by Paolo Masetti. * * */ #include #include #include #include #include #include #include #include #include #if defined(_WIN32) #include #endif #if (defined(__unix__) || defined(unix)) && !defined(USG) #include #endif #include #include #include "rcxtty.h" #include "keepalive.h" #include #define MAX_DATA_CHUNK 0xf8 //!< maximum data bytes/packet for boot protocol #define XMIT_RETRIES 5 //!< number of packet transmit retries #define REPLY_TIMEOUT 750000 //!< timeout for reply #define BYTE_TIME (1000*LNP_BYTE_TIME) //!< time to transmit a byte. #define PROG_MIN 1 #define PROG_MAX 8 #define ADDR_MIN 0 #define ADDR_MAX 15 #define DEFAULT_DEST 0 #define DEFAULT_PROGRAM PROG_MIN #define DEFAULT_SRCPORT 0 #define DEFAULT_PRIORITY 10 typedef enum { CMDacknowledge, //!< 1: CMDdelete, //!< 1+ 1: b[nr] CMDcreate, //!< 1+12: b[nr] s[textsize] s[datasize] s[bsssize] s[stacksize] s[start] b[prio] CMDoffsets, //!< 1+ 7: b[nr] s[text] s[data] s[bss] CMDdata, //!< 1+>3: b[nr] s[offset] array[data] CMDrun, //!< 1+ 1: b[nr] CMDirmode, //!< 1+ 1: b[0=near/1=far] CMDsethost, //!< 1+ 1: b[hostaddr] CMDlast //!< ? } packet_cmd_t; #if (defined(__sun__) && defined(__svr4__)) || defined(BSD) // Solaris||BSD #undef HAVE_GETOPT_LONG #else #define HAVE_GETOPT_LONG 1 #endif #ifdef HAVE_GETOPT_LONG #include static const struct option long_options[]={ {"rcxaddr",required_argument,0,'r'}, {"program",required_argument,0,'p'}, {"delete", required_argument,0,'d'}, {"srcport",required_argument,0,'s'}, {"tty", required_argument,0,'t'}, {"irmode", required_argument,0,'i'}, {"node" , required_argument,0,'n'}, {"execute",no_argument ,0,'e'}, {"verbose",no_argument ,0,'v'}, {0 ,0 ,0,0 } }; #else // HAVE_GETOPT_LONG #define getopt_long(ac, av, opt, lopt, lidx) (getopt((ac), (av), (opt))) #endif // HAVE_GETOPT_LONG volatile int receivedAck=0; volatile unsigned short relocate_to=0; unsigned int rcxaddr = DEFAULT_DEST, prog = DEFAULT_PROGRAM, srcport = DEFAULT_SRCPORT, hostaddr = DEFAULT_DEST, irmode = -1; int run_flag=0; int verbose_flag=0; int tty_usb=0; int pdelete_flag=0; int hostaddr_flag=0; void io_handler(void); /*! blocking I/R write. *! return number of bytes written, or negative for error. */ int lnp_logical_write(const void *data, size_t length) { // With Win32 we are using Blocking Write by default #if !defined(_WIN32) fd_set fds; // wait for transmission // do { FD_ZERO(&fds); FD_SET(rcxFD(),&fds); } while(select(rcxFD()+1,NULL,&fds,NULL,NULL)<1); #endif // transmit // #if defined(LINUX) || defined(linux) if (tty_usb == 0) #endif keepaliveRenew(); return mywrite(rcxFD(), data, length)!=length; } //! send a LNP layer 0 packet of given length /*! \return 0 on success. */ int lnp_assured_write(const unsigned char *data, unsigned char length, unsigned char dest, unsigned char srcport) { int i; struct timeval timeout,now; unsigned long total,elapsed; for(i=0; i 10000*LNP_BYTE_TIMEOUT) { if(verbose_flag) fprintf(stderr,"\n#time %lu ",diff); lnp_integrity_reset(); } #if defined(_WIN32) // Remember, USB support only in WIN32 environments. if (tty_usb == 0) { ReadFile(rcxFD(), buffer, sizeof(buffer), &len, NULL); } else { struct timeval timeout, timenow; unsigned long total, elapsed; gettimeofday(&timeout,0); total = REPLY_TIMEOUT+(long)sizeof(buffer)*BYTE_TIME; while(len == 0) { ReadFile(rcxFD(), buffer, sizeof(buffer), &len, NULL); gettimeofday(&timenow, 0); // calculate elapsed time as usual elapsed=1000000*(timenow.tv_sec - timeout.tv_sec ) + (timenow.tv_usec - timeout.tv_usec); if(elapsed > total) break; } } #else len=read(rcxFD(),buffer,sizeof(buffer)); #endif for(i=0; itext_size+lx->data_size; if(verbose_flag) fputs("\ndata ",stderr); buffer[0]=CMDdata; buffer[1]=prog-1; for(i=0; iMAX_DATA_CHUNK) chunkSize=MAX_DATA_CHUNK; buffer[2]= i >> 8; buffer[3]= i & 0xff; memcpy(buffer+4,lx->text + i,chunkSize); if(lnp_assured_write(buffer,chunkSize+4,rcxaddr,srcport)) { fputs("error downloading program\n",stderr); exit(-1); } } } int main(int argc, char **argv) { lx_t lx; // the brickOS executable char *filename; int opt; #ifdef HAVE_GETOPT_LONG int option_index; #endif unsigned char buffer[256+3]=""; char *tty=NULL; while((opt=getopt_long(argc, argv, "r:p:d:s:t:i:n:ev", (struct option *)long_options, &option_index) )!=-1) { switch(opt) { case 'e': run_flag=1; break; case 'r': sscanf(optarg,"%d",&rcxaddr); if (rcxaddr > ADDR_MAX || rcxaddr < ADDR_MIN) { fprintf(stderr, "LNP host address not in range 0..15\n"); return -1; } rcxaddr = (rcxaddr << 4) & CONF_LNP_HOSTMASK; break; case 'p': sscanf(optarg,"%d",&prog); break; case 's': sscanf(optarg,"%d",&srcport); if (srcport > ADDR_MAX || srcport < ADDR_MIN) { fprintf(stderr, "LNP port number not in range 0..15\n"); return -1; } break; case 't': sscanf(optarg,"%s",buffer); break; case 'i': sscanf(optarg,"%x",&irmode); break; case 'd': sscanf(optarg,"%d",&prog); pdelete_flag=1; break; case 'n': sscanf(optarg,"%d",&hostaddr); if (hostaddr > ADDR_MAX || hostaddr < ADDR_MIN) { fprintf(stderr, "LNP host address not in range 0..15\n"); return -1; } hostaddr_flag=1; break; case 'v': verbose_flag=1; break; } } if (prog > PROG_MAX || prog < PROG_MIN) { fprintf(stderr, "Program not in range 1..8\n"); return -1; } // load executable // if(((argc-optind < 1) && !(pdelete_flag || hostaddr_flag)) || ((argc-optind > 0 ) && (pdelete_flag || hostaddr_flag))) { char *usage_string = "Options:\n" " -p , --program= set destination program to \n" " -r , --rcxaddr= send to RCX host address \n" " -s , --srcport= send to RCX source port \n" " -t , --tty= set IR Tower com port \n" #if defined(_WIN32) " -t , --tty= set IR Tower USB mode \n" #else " (if \"usb\" is in the port, use USB mode)\n" #endif " -i<0/1> , --irmode=<0/1> set IR mode near(0)/far(1) on RCX\n" " -e , --execute execute program after download\n" " -v , --verbose verbose mode\n" "\nCommands:\n" " -d , --delete= delete program from memory\n" " -n , --node= set LNP host address in brick\n" "\n" "Default COM port or USB support can be set using environment variable RCXTTY.\n" "Eg:\tset RCXTTY=COM2\n" "\tset RCXTTY=USB\n" "\n" ; fprintf(stderr, "usage: %s [options] [command | file.lx]\n", argv[0]); fprintf(stderr, usage_string); return -1; } // Ignore filename if -dn or -na given if (!(pdelete_flag || hostaddr_flag)) { filename=argv[optind++]; if(lx_read(&lx,filename)) { fprintf(stderr,"unable to load brickOS executable from %s.\n",filename); return -1; } } // Moved tty device setting for a new option on command line if (buffer[0]) tty=buffer; if (!tty) tty = getenv(TTY_VARIABLE); if (!tty) tty = DEFAULTTTY; // Check if USB IR tower is selected. #if defined(_WIN32) if (stricmp(tty, "usb")==0) { tty_usb = 1; if(verbose_flag) fputs("\n\n Hary Mahesan - LEGO USB IR Tower Mode\n\n",stderr); tty="\\\\.\\legotower1"; // Set the correct usb tower if you have more than one (unlikely). } #elif defined(LINUX) || defined(linux) /* If the tty string contains "usb", e.g. /dev/usb/lego0, we */ /* assume it is the USB tower. /dev/usb/lego0 is the default name of */ /* the device in the LegoUSB (http://legousb.sourceforge.net) project. */ /* If yours doesn't contain the "usb" string, just link it. */ if (strstr(tty,"usb") !=0) { tty_usb=1; if (verbose_flag) fputs("\nC.P. Chan & Tyler Akins - USB IR Tower Mode for Linux.\n",stderr); } #endif LNPinit(tty); if (verbose_flag) fputs("\nLNP Initialized...\n",stderr); lnp_addressing_set_handler(0,ahandler); if(verbose_flag) fprintf(stderr,"loader hostaddr=0x%02x hostmask=0x%02x portmask=0x%02x\n", rcxaddr & 0x00ff, LNP_HOSTMASK & 0x00ff, srcport & 0x00ff); // Set IR mode if (irmode != -1) { buffer[0]=CMDirmode; buffer[1]=irmode; if(lnp_assured_write(buffer,2,rcxaddr,srcport)) { fputs("error setting IR mode to far\n",stderr); return -1; } } // Set LNP host address if (hostaddr_flag) { if(verbose_flag) fputs("\nset LNP host address", stderr); buffer[0] = CMDsethost; buffer[1] = hostaddr; if(lnp_assured_write(buffer,2,rcxaddr,srcport)) { fputs("error setting host address\n",stderr); return -1; } fprintf(stderr, "LNP host address set to %d\n", hostaddr); return 0; } if(verbose_flag) fputs("\ndelete",stderr); buffer[0]=CMDdelete; buffer[1]=prog-1; // prog 0 if(lnp_assured_write(buffer,2,rcxaddr,srcport)) { fputs("error deleting program\n",stderr); return -1; } // All done if -dn given if (pdelete_flag) { fprintf(stderr, "P%d deleted\n", prog); return 0; } if(verbose_flag) fputs("\ncreate ",stderr); buffer[ 0]=CMDcreate; buffer[ 1]=prog-1; // prog 0 buffer[ 2]=lx.text_size>>8; buffer[ 3]=lx.text_size & 0xff; buffer[ 4]=lx.data_size>>8; buffer[ 5]=lx.data_size & 0xff; buffer[ 6]=lx.bss_size>>8; buffer[ 7]=lx.bss_size & 0xff; buffer[ 8]=lx.stack_size>>8; buffer[ 9]=lx.stack_size & 0xff; buffer[10]=lx.offset >> 8; // start offset from text segment buffer[11]=lx.offset & 0xff; buffer[12]=DEFAULT_PRIORITY; if(lnp_assured_write(buffer,13,rcxaddr,srcport)) { fputs("error creating program\n",stderr); return -1; } // relocation target address in relocate_to // lx_relocate(&lx,relocate_to); lnp_download(&lx); fprintf(stderr, "\n"); if (run_flag) { if(verbose_flag) fputs("\nrun ",stderr); buffer[0]=CMDrun; buffer[1]=prog-1; // prog 0 if(lnp_assured_write(buffer,2,rcxaddr,srcport)) { fputs("error running program\n",stderr); return -1; } } return 0; }