/* * firmware handling tools * * Copyright (C) 2001 Kees Cook * cook@cpoint.net, http://outflux.net/ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include #include #include #include #include #include #include #include "scsi_iface.h" #include "scsi_tools.h" #include "firmware.h" int write_buffer(int fd, int timeout, u_char lun, u_char mode, u_char buffer_id, u_char bufoff1, u_char bufoff2, u_char bufoff3, u_char paraml1, u_char paraml2, u_char paraml3, u_char control, int datalen, u_char * data) { static u_char buf[SG_LEN + MAX_PACKET]; /* copy SCSI cmd parameters into buffer */ buf[SG_LEN+0]=0x3b; buf[SG_LEN+1]=(lun<<5) + mode; buf[SG_LEN+2]=buffer_id; buf[SG_LEN+3]=bufoff1; buf[SG_LEN+4]=bufoff2; buf[SG_LEN+5]=bufoff3; buf[SG_LEN+6]=paraml1; buf[SG_LEN+7]=paraml2; buf[SG_LEN+8]=paraml3; buf[SG_LEN+9]=control; /* copy to-SCSI-device data into buffer */ if (datalen && data) { memcpy(buf+SG_LEN+10,data,datalen); } /* off it goes! */ return (waitfor_scsi_cmd(fd,10,datalen,buf,0,NULL)==0) ? OKAY : FAILED; } int prog_mode(int fd) { return write_buffer(fd, 1, /* timeout */ 0,5,0, /* lun, mode, buff id */ 0,0,0, /* buffer offset */ 0,0,0, /* param list */ 0x80, /* control */ 0, NULL); /* data */ } int restart_fas(int fd) { return write_buffer(fd, 1, /* timeout */ 0,5,4, /* lun, mode, buff id */ 0,0,0, /* buffer offset */ 0,0,0, /* param list */ 0x80, /* control */ 0, NULL); /* data */ } int select_unit(int fd, u_char uflags, int len) { return write_buffer(fd, 1, /* timeout */ 0,5,1, /* lun, mode, buff id */ uflags,0,0, /* buffer offset */ (u_char)(len >> 16), (u_char)(len >> 8), (u_char)(len), /* param list*/ 0x80, /* control */ 0, NULL); /* data */ } int send_chunk(int fd, int offset, int len, u_char * data) { return write_buffer(fd, 180, /* timeout */ 0,5,2, /* lun, mode, buff id */ (u_char)(offset >> 16), (u_char)(offset >> 8), (u_char)(offset), /* buffer offset */ (u_char)(len >> 16), (u_char)(len >> 8), (u_char)(len), /* param list*/ 0x80, /* control */ len, data+offset); /* data */ } /* returns 3 states: OKAY = success FAILED = no operation BADSTATE= interrupted */ #define MIN(x,y) ((x)<(y) ? (x) : (y)) int program_unit(int fd, int imagelen, u_char * image) { int remaining, send, offset; int chunks, sent; if (!select_unit(fd,0,imagelen)) { fprintf(stderr,"FAILED: selecting unit\n"); return FAILED; } remaining=imagelen; offset=0; chunks=imagelen/MAX_PUSH; sent=1; while (remaining>0) { if (sent==chunks) { /* sending last block */ printf("\tCommitting firmware ...\n"); } else { switch (sent) { case 1: /* first block starts erase */ printf("\tErasing firmware ...\n"); break; case 2: /* start counting up the blocks */ printf("\tUploading firmware chunks ...\n"); default: printf("\t\t%d/%d\r",sent,chunks); break; } } fflush(NULL); send=MIN(remaining,MAX_PUSH); if (send_chunk(fd,offset,send,image)!=OKAY) { fprintf(stderr,"\nFAILED: firmware upload\n"); return BADSTATE; } remaining-=send; offset+=send; sent++; } return OKAY; } /* takes a string to a product name, returns a string for a filename guess for the firmware.prm file */ char * build_firmware_filename(char * product_id) { static char buf[64]; int i; char * ptr=product_id; char * slash; if (!product_id) return NULL; /* If there is a slash in the product name, skip past the slash */ slash=rindex(ptr,'/'); if (slash) { ptr=slash+1; } /* copy all characters except '-', as lower case */ i=0; while (*ptr && i<60 /* && *ptr!=' ' */) { if (*ptr == '-') { ptr++; continue; } buf[i++]=tolower(*ptr); ptr++; } /* add ".prm" to the end */ buf[i++]='.'; buf[i++]='p'; buf[i++]='r'; buf[i++]='m'; buf[i++]='\0'; return buf; } /* Copies a string until white space or out of chars */ void strnwcpy(char *dest, char *src, int n) { while(n-- && *src && *src != ' ') *dest++ = *src++; *dest=0; } /* Takes length and image to loaded firmware Returns unit ID info from firmware file */ unit_id *verify_image(firmware_img * firmware) { char buf[128]; char * ptr; int i; u_char last, sum=0; if (!firmware) { fprintf(stderr,"Verifying NULL firmware image?!\n"); return NULL; } if (!(ptr=firmware->image)) { fprintf(stderr,"Firmware has NULL image?!\n"); return NULL; } if (firmware->length < 128) { fprintf(stderr,"Firmware image less than 128 bytes?!\n"); return NULL; } /* first, let's run a checksum on the image ... */ /* This is an 8-bit checksum. The last byte should make sure that the chksum results in a zero. */ for (i=0;ilength;i++) { last=firmware->image[i]; sum+=last; } if (sum!=0) { fprintf(stderr,"Image fails checksum! Got 0x%02x, " "expected 0x00 (last byte was 0x%02x)\n",sum,last); return NULL; } /* rebuild the image product info area in the case of double images from dual prom */ if ( ptr[0] == ptr[1] && ptr[2] == ptr[3] && ptr[4] == ptr[5] ) { for (i=0; i < 128; i++) buf[i]=ptr[i*2]; ptr=buf; } /* find firmware version */ for (i=0; i<125; i++) { if (ptr[i] == 'V' && isdigit(ptr[i+1]) && ptr[i+2]=='.') break; } if (i==125) return NULL; /* failed to find a firmware image */ /* copy the product name */ strnwcpy(firmware->unit.name,ptr,NAMLEN); firmware->unit.name[NAMLEN]='\0'; /* yank firmware version text */ firmware->unit.fw_ver[0]=ptr[i++]; firmware->unit.fw_ver[1]=ptr[i++]; firmware->unit.fw_ver[2]=ptr[i++]; firmware->unit.fw_ver[3]=ptr[i++]; firmware->unit.fw_ver[4]='\0'; return &firmware->unit; } /* takes a filename, returns a ptr to the firmware image */ firmware_img * load_firmware(char * filename) { struct stat stats; firmware_img * firmware; if (!(firmware=(firmware_img*)malloc(sizeof(firmware_img)))) { perror("load_firmware malloc"); return NULL; } /* clear the name space */ firmware->unit.name[0]='\0'; firmware->unit.fw_ver[0]='\0'; /* open the file */ if ((firmware->fd=open(filename,O_RDONLY))<0) { fprintf(stderr,"open('%s'): %s\n",filename,strerror(errno)); free(firmware); return NULL; } /* find out file length. could also lseek */ if (fstat(firmware->fd,&stats)) { fprintf(stderr,"fstat('%s'): %s\n",filename,strerror(errno)); free(firmware); return NULL; } /* mmap the memory */ firmware->length=stats.st_size; if ((int)(firmware->image=(u_char*)mmap(NULL,firmware->length, PROT_READ,MAP_PRIVATE, firmware->fd,0))==-1) { fprintf(stderr,"mmap('%s'): %s\n",filename,strerror(errno)); free(firmware); return NULL; } return firmware; } void unload_firmware(firmware_img * firmware) { if (!firmware) { fprintf(stderr,"unload_firmware got NULL pointer?!\n"); return; } if (munmap(firmware->image,firmware->length)) { perror("munmap"); } if (close(firmware->fd)) { perror("close"); } /* free the memory */ free(firmware); } /* this function is ONLY to be run by "upload_now" */ int run_upload(scsi_info *dev, firmware_img *firmware) { int rc, i; sleep(1); /* because that's what cdupdate does... */ /* make sure our device is ready again */ for (i=0;i<4;i++) { if (scsi_unitready(dev)) break; } if (i==4) { printf("FAILURE: unit did not become ready\n"); /* didn't recover from setting programming mode */ return FAILED; } if ((rc=program_unit(dev->fd,firmware->length,firmware->image))!=OKAY) { printf("FAILURE: unit did not take firmware\n"); return rc; } return OKAY; } /* This function assumes everthing has passed verification with "choose_device" and "choose_firmware". DO NOT call this without image checking! */ int upload_now(scsi_info *dev, firmware_img *firmware) { int rc,i; if (!dev) { fprintf(stderr,"AGH: upload_now had a NULL dev?!\n"); return FAILED; } if (!firmware) { fprintf(stderr,"AGH: upload_now had a NULL firmware?!\n"); return FAILED; } printf("\tPreparing for update...\n"); /* make sure our device is ready */ for (i=0;i<4;i++) { if (scsi_unitready(dev)) break; } if (i==4) { printf("FAILURE: unit did not become ready\n"); return FAILED; /* unit never came ready */ } /* kick it into programming mode */ if (prog_mode(dev->fd)!=OKAY) { printf("FAILURE: could not enter programming mode\n"); return FAILED; /* unit didn't go into programming mode */ } if ((rc=run_upload(dev,firmware))!=BADSTATE) { if (rc!=OKAY) printf("FAILURE: run_upload croaked\n"); /* go back to fas mode */ printf("\tFinishing update ...\n"); restart_fas(dev->fd); sleep(2); /* are we back? */ for (i=0;i<4;i++) { if (scsi_unitready(dev)) break; } if (i==4) { printf("FAILURE: unit never came ready\n"); /* didn't recover. on a success, that's a failure, on an upload failure, that's real bad */ return (rc==FAILED ? BADSTATE : FAILED); } } return rc; }