rockbox/apps/plugins/firmware_flash.c
Jörg Hohensohn 60589066dd I release this plugin to cvs, now that I released new firmware images (see http://joerg.hohensohn.bei.t-online.de/archos/flash) with my new CRC check.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3953 a1c6a512-1295-4272-9138-f99709370657
2003-09-09 07:02:53 +00:00

854 lines
22 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Plugin for reprogramming the whole Flash ROM chip with a new content.
* !!! DON'T MESS WITH THIS CODE UNLESS YOU'RE ABSOLUTELY SHURE WHAT YOU DO !!!
*
* Copyright (C) 2003 Jörg Hohensohn [IDC]Dragon
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "plugin.h"
#ifndef SIMULATOR
/* define DUMMY if you only want to "play" with the UI, does no harm */
/* #define DUMMY */
#ifndef UINT8
#define UINT8 unsigned char
#endif
#ifndef UINT16
#define UINT16 unsigned short
#endif
#ifndef UINT32
#define UINT32 unsigned long
#endif
#if defined(ARCHOS_PLAYER)
#define FILENAME "/firmware_play.bin"
#define KEEP VERSION_ADR /* keep the firmware version */
#elif defined(ARCHOS_RECORDER)
#define FILENAME "/firmware_rec.bin"
#define KEEP MASK_ADR /* keep the mask value */
#elif defined(ARCHOS_FMRECORDER)
#define FILENAME "/firmware_fm.bin"
#define KEEP MASK_ADR /* keep the mask value */
#else
#error ("No known platform given!")
#endif
/* result of the CheckFirmwareFile() function */
typedef enum
{
eOK = 0,
eFileNotFound, /* errors from here on */
eTooBig,
eTooSmall,
eReadErr,
eBadContent,
eCrcErr,
} tCheckResult;
typedef struct
{
UINT8 manufacturer;
UINT8 id;
int size;
char name[32];
} tFlashInfo;
static struct plugin_api* rb; /* here is a global api struct pointer */
#define MASK_ADR 0xFC /* position of hardware mask value in Flash */
#define VERSION_ADR 0xFE /* position of firmware version value in Flash */
#define SEC_SIZE 4096 /* size of one flash sector */
static UINT8* sector; /* better not place this on the stack... */
static volatile UINT8* FB = (UINT8*)0x02000000; /* Flash base address */
/***************** Flash Functions *****************/
/* read the manufacturer and device ID */
bool ReadID(volatile UINT8* pBase, UINT8* pManufacturerID, UINT8* pDeviceID)
{
UINT8 not_manu, not_id; /* read values before switching to ID mode */
UINT8 manu, id; /* read values when in ID mode */
pBase = (UINT8*)((UINT32)pBase & 0xFFF80000); /* down to 512k align */
/* read the normal content */
not_manu = pBase[0]; /* should be 'A' (0x41) and 'R' (0x52) */
not_id = pBase[1]; /* from the "ARCH" marker */
pBase[0x5555] = 0xAA; /* enter command mode */
pBase[0x2AAA] = 0x55;
pBase[0x5555] = 0x90; /* ID command */
rb->sleep(HZ/50); /* Atmel wants 20ms pause here */
manu = pBase[0];
id = pBase[1];
pBase[0] = 0xF0; /* reset flash (back to normal read mode) */
rb->sleep(HZ/50); /* Atmel wants 20ms pause here */
/* I assume success if the obtained values are different from
the normal flash content. This is not perfectly bulletproof, they
could theoretically be the same by chance, causing us to fail. */
if (not_manu != manu || not_id != id) /* a value has changed */
{
*pManufacturerID = manu; /* return the results */
*pDeviceID = id;
return true; /* success */
}
return false; /* fail */
}
/* erase the sector which contains the given address */
bool EraseSector(volatile UINT8* pAddr)
{
#ifdef DUMMY
(void)pAddr; /* prevents warning */
return true;
#else
volatile UINT8* pBase = (UINT8*)((UINT32)pAddr & 0xFFF80000); /* round down to 512k align */
unsigned timeout = 43000; /* the timeout loop should be no less than 25ms */
pBase[0x5555] = 0xAA; /* enter command mode */
pBase[0x2AAA] = 0x55;
pBase[0x5555] = 0x80; /* erase command */
pBase[0x5555] = 0xAA; /* enter command mode */
pBase[0x2AAA] = 0x55;
*pAddr = 0x30; /* erase the sector */
/* I counted 7 instructions for this loop -> min. 0.58 us per round */
/* Plus memory waitstates it will be much more, gives margin */
while (*pAddr != 0xFF && --timeout); /* poll for erased */
return (timeout != 0);
#endif
}
/* address must be in an erased location */
inline bool ProgramByte(volatile UINT8* pAddr, UINT8 data)
{
#ifdef DUMMY
(void)pAddr; /* prevents warnings */
(void)data;
return true;
#else
unsigned timeout = 35; /* the timeout loop should be no less than 20us */
if (~*pAddr & data) /* just a safety feature, not really necessary */
return false; /* can't set any bit from 0 to 1 */
FB[0x5555] = 0xAA; /* enter command mode */
FB[0x2AAA] = 0x55;
FB[0x5555] = 0xA0; /* byte program command */
*pAddr = data;
/* I counted 7 instructions for this loop -> min. 0.58 us per round */
/* Plus memory waitstates it will be much more, gives margin */
while (*pAddr != data && --timeout); /* poll for programmed */
return (timeout != 0);
#endif
}
/* this returns true if supported and fills the info struct */
bool GetFlashInfo(tFlashInfo* pInfo)
{
rb->memset(pInfo, 0, sizeof(tFlashInfo));
if (!ReadID(FB, &pInfo->manufacturer, &pInfo->id))
return false;
if (pInfo->manufacturer == 0xBF) /* SST */
{
if (pInfo->id == 0xD6)
{
pInfo->size = 256* 1024; /* 256k */
rb->strcpy(pInfo->name, "SST39VF020");
return true;
}
else if (pInfo->id == 0xD7)
{
pInfo->size = 512* 1024; /* 512k */
rb->strcpy(pInfo->name, "SST39VF040");
return true;
}
else
return false;
}
return false;
}
/*********** Utility Functions ************/
/* Tool function to calculate a CRC32 across some buffer */
/* third argument is either 0xFFFFFFFF to start or value from last piece */
unsigned crc_32(unsigned char* buf, unsigned len, unsigned crc32)
{
/* CCITT standard polynomial 0x04C11DB7 */
static const unsigned crc32_lookup[16] =
{ /* lookup table for 4 bits at a time is affordable */
0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD
};
unsigned char byte;
unsigned t;
while (len--)
{
byte = *buf++; /* get one byte of data */
/* upper nibble of our data */
t = crc32 >> 28; /* extract the 4 most significant bits */
t ^= byte >> 4; /* XOR in 4 bits of data into the extracted bits */
crc32 <<= 4; /* shift the CRC register left 4 bits */
crc32 ^= crc32_lookup[t]; /* do the table lookup and XOR the result */
/* lower nibble of our data */
t = crc32 >> 28; /* extract the 4 most significant bits */
t ^= byte & 0x0F; /* XOR in 4 bits of data into the extracted bits */
crc32 <<= 4; /* shift the CRC register left 4 bits */
crc32 ^= crc32_lookup[t]; /* do the table lookup and XOR the result */
}
return crc32;
}
/*********** Firmware File Functions ************/
tCheckResult CheckFirmwareFile(char* filename, int chipsize)
{
int i;
int fd;
int fileleft; /* size info, how many left for reading */
int fileread = 0; /* total size as read from the file */
int read_now; /* how many to read for this sector */
int got_now; /* how many gotten for this sector */
unsigned crc32 = 0xFFFFFFFF; /* CCITT init value */
unsigned file_crc; /* CRC value read from file */
bool has_crc;
fd = rb->open(filename, O_RDONLY);
if (fd < 0)
return eFileNotFound;
fileleft = rb->filesize(fd);
if (fileleft > chipsize)
{
rb->close(fd);
return eTooBig;
}
else if (fileleft < 50000) /* give it some reasonable lower limit */
{
rb->close(fd);
return eTooSmall;
}
if (fileleft == 256*1024)
{ // original dumped firmware file has no CRC
has_crc = false;
}
else
{
has_crc = true;
fileleft -= sizeof(unsigned); // exclude the last 4 bytes
}
/* do some sanity checks */
got_now = rb->read(fd, sector, SEC_SIZE); /* read first sector */
fileread += got_now;
fileleft -= got_now;
if (got_now != SEC_SIZE)
{
rb->close(fd);
return eReadErr;
}
if (has_crc)
crc32 = crc_32(sector, SEC_SIZE, crc32); /* checksum */
/* compare some bytes which have to be identical */
if (*(UINT32*)sector != 0x41524348) /* "ARCH" */
{
rb->close(fd);
return eBadContent;
}
for (i = 0x30; i<MASK_ADR-1; i++) /* leave one byte for me */
{
if (sector[i] != FB[i])
{
rb->close(fd);
return eBadContent;
}
}
/* check if we can read the whole file, and do checksum */
do
{
read_now = MIN(SEC_SIZE, fileleft);
got_now = rb->read(fd, sector, read_now);
fileread += got_now;
fileleft -= got_now;
if (read_now != got_now)
{
rb->close(fd);
return eReadErr;
}
if (has_crc)
{
crc32 = crc_32(sector, got_now, crc32); /* checksum */
}
} while (fileleft);
if (has_crc)
{
got_now = rb->read(fd, &file_crc, sizeof(file_crc));
if (got_now != sizeof(file_crc))
{
rb->close(fd);
return eReadErr;
}
}
/* must be EOF now */
got_now = rb->read(fd, sector, SEC_SIZE);
rb->close(fd);
if (got_now != 0)
return eReadErr;
if (has_crc && file_crc != crc32)
return eCrcErr;
return eOK;
}
/* returns the # of failures, 0 on success */
unsigned ProgramFirmwareFile(char* filename, int chipsize)
{
int i, j;
int fd;
int read = SEC_SIZE; /* how many for this sector */
UINT16 keep = *(UINT16*)(FB + KEEP); /* we must keep this! */
unsigned failures = 0;
fd = rb->open(filename, O_RDONLY);
if (fd < 0)
return false;
for (i=0; i<chipsize; i+=SEC_SIZE)
{
if (!EraseSector(FB + i))
{
/* nothing we can do, let the programming count the errors */
}
if (read == SEC_SIZE) /* not EOF yet */
{
read = rb->read(fd, sector, SEC_SIZE);
if (i==0)
{ /* put original value back in */
*(UINT16*)(sector + KEEP) = keep;
}
for (j=0; j<read; j++)
{
if (!ProgramByte(FB + i + j, sector[j]))
{
failures++;
}
}
}
}
rb->close(fd);
return failures;
}
/* returns the # of failures, 0 on success */
unsigned VerifyFirmwareFile(char* filename)
{
int i=0, j;
int fd;
int read = SEC_SIZE; /* how many for this sector */
unsigned failures = 0;
fd = rb->open(filename, O_RDONLY);
if (fd < 0)
return false;
do
{
read = rb->read(fd, sector, SEC_SIZE);
for (j=0; j<read; j++)
{
/* position of keep value is no error */
if (FB[i] != sector[j] && i != KEEP && i != (KEEP+1))
{
failures++;
}
i++;
}
}
while (read == SEC_SIZE);
rb->close(fd);
return failures;
}
/***************** User Interface Functions *****************/
int WaitForButton(void)
{
int button;
do
{
button = rb->button_get(true);
} while (button & BUTTON_REL);
return button;
}
#ifdef HAVE_LCD_BITMAP
/* Recorder implementation */
/* helper for DoUserDialog() */
void ShowFlashInfo(tFlashInfo* pInfo)
{
char buf[32];
if (!pInfo->manufacturer)
{
rb->lcd_puts(0, 0, "Flash: M=?? D=??");
rb->lcd_puts(0, 1, "Impossible to program");
}
else
{
rb->snprintf(buf, sizeof(buf), "Flash: M=%02x D=%02x",
pInfo->manufacturer, pInfo->id);
rb->lcd_puts(0, 0, buf);
if (pInfo->size)
{
rb->lcd_puts(0, 1, pInfo->name);
rb->snprintf(buf, sizeof(buf), "Size: %d KB", pInfo->size / 1024);
rb->lcd_puts(0, 2, buf);
}
else
{
rb->lcd_puts(0, 1, "Unsupported chip");
}
}
rb->lcd_update();
}
/* Kind of our main function, defines the application flow. */
void DoUserDialog(void)
{
tFlashInfo FlashInfo;
char buf[32];
int button;
int rc; /* generic return code */
int memleft;
unsigned boot_crc;
rb->lcd_setfont(FONT_SYSFIXED);
/* check boot ROM */
boot_crc = crc_32((unsigned char*)0x0, 64*1024, 0xFFFFFFFF);
if (boot_crc != 0x56DBA4EE) /* Version 1 */
{ /* no support for any other yet */
rb->splash(HZ*3, 0, true, "Wrong boot ROM");
return; /* exit */
}
/* "allocate" memory */
sector = rb->plugin_get_buffer(&memleft);
if (memleft < SEC_SIZE) /* need buffer for a flash sector */
{
rb->splash(HZ*3, 0, true, "Out of memory");
return; /* exit */
}
rc = GetFlashInfo(&FlashInfo);
ShowFlashInfo(&FlashInfo);
if (FlashInfo.size == 0) /* no valid chip */
{
rb->splash(HZ*3, 0, true, "Sorry!");
return; /* exit */
}
rb->lcd_puts(0, 3, "using file:");
rb->lcd_puts(0, 4, FILENAME);
rb->lcd_puts(0, 6, "[F1] to check file");
rb->lcd_puts(0, 7, "other key to exit");
rb->lcd_update();
button = WaitForButton();
if (button != BUTTON_F1)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "checking...");
rb->lcd_update();
rc = CheckFirmwareFile(FILENAME, FlashInfo.size);
rb->lcd_puts(0, 0, "checked:");
switch (rc)
{
case eOK:
rb->lcd_puts(0, 1, "File OK.");
break;
case eFileNotFound:
rb->lcd_puts(0, 1, "File not found.");
rb->lcd_puts(0, 2, "Put this in root:");
rb->lcd_puts(0, 4, FILENAME);
break;
case eTooBig:
rb->lcd_puts(0, 1, "File too big,");
rb->lcd_puts(0, 2, "larger than chip.");
break;
case eTooSmall:
rb->lcd_puts(0, 1, "File too small.");
rb->lcd_puts(0, 2, "Incomplete?");
break;
case eReadErr:
rb->lcd_puts(0, 1, "Read error.");
break;
case eBadContent:
rb->lcd_puts(0, 1, "File invalid.");
rb->lcd_puts(0, 2, "Sanity check fail.");
break;
case eCrcErr:
rb->lcd_puts(0, 1, "File invalid.");
rb->lcd_puts(0, 2, "CRC check failed,");
rb->lcd_puts(0, 3, "checksum mismatch.");
break;
default:
rb->lcd_puts(0, 1, "Check failed.");
break;
}
if (rc == eOK)
{
rb->lcd_puts(0, 6, "[F2] to program");
rb->lcd_puts(0, 7, "other key to exit");
}
else
{ /* error occured */
rb->lcd_puts(0, 6, "Any key to exit");
}
rb->lcd_update();
button = WaitForButton();
if (button != BUTTON_F2 || rc != eOK)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Program all Flash?");
rb->lcd_puts(0, 1, "Are you shure?");
rb->lcd_puts(0, 2, "If it goes wrong,");
rb->lcd_puts(0, 3, "it kills your box!");
rb->lcd_puts(0, 4, "See documentation.");
rb->lcd_puts(0, 6, "[F3] to proceed");
rb->lcd_puts(0, 7, "other key to exit");
rb->lcd_update();
button = WaitForButton();
if (button != BUTTON_F3)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Programming...");
rb->lcd_update();
rc = ProgramFirmwareFile(FILENAME, FlashInfo.size);
if (rc)
{ /* errors */
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Panic:");
rb->lcd_puts(0, 1, "Programming fail!");
rb->snprintf(buf, sizeof(buf), "%d errors", rc);
rb->lcd_puts(0, 2, buf);
rb->lcd_update();
button = WaitForButton();
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Verifying...");
rb->lcd_update();
rc = VerifyFirmwareFile(FILENAME);
rb->lcd_clear_display();
if (rc == 0)
{
rb->lcd_puts(0, 0, "Verify OK.");
}
else
{
rb->lcd_puts(0, 0, "Panic:");
rb->lcd_puts(0, 1, "Verify fail!");
rb->snprintf(buf, sizeof(buf), "%d errors", rc);
rb->lcd_puts(0, 2, buf);
}
rb->lcd_puts(0, 7, "Any key to exit");
rb->lcd_update();
button = WaitForButton();
}
#else /* HAVE_LCD_BITMAP */
/* Player implementation */
/* helper for DoUserDialog() */
void ShowFlashInfo(tFlashInfo* pInfo)
{
char buf[32];
if (!pInfo->manufacturer)
{
rb->lcd_puts_scroll(0, 0, "Flash: M=? D=?");
rb->lcd_puts_scroll(0, 1, "Impossible to program");
WaitForButton();
}
else
{
rb->snprintf(buf, sizeof(buf), "Flash: M=%02x D=%02x",
pInfo->manufacturer, pInfo->id);
rb->lcd_puts_scroll(0, 0, buf);
if (pInfo->size)
{
rb->snprintf(buf, sizeof(buf), "Size: %d KB", pInfo->size / 1024);
rb->lcd_puts_scroll(0, 1, buf);
}
else
{
rb->lcd_puts_scroll(0, 1, "Unsupported chip");
WaitForButton();
}
}
}
void DoUserDialog(void)
{
tFlashInfo FlashInfo;
char buf[32];
int button;
int rc; /* generic return code */
int memleft;
unsigned boot_crc;
/* check boot ROM */
boot_crc = crc_32((unsigned char*)0x0, 64*1024, 0xFFFFFFFF);
if (boot_crc != 0x56DBA4EE) /* Version 1 */
{ /* no support for any other yet */
rb->splash(HZ*3, 0, true, "Wrong boot ROM");
return; /* exit */
}
/* "allocate" memory */
sector = rb->plugin_get_buffer(&memleft);
if (memleft < SEC_SIZE) /* need buffer for a flash sector */
{
rb->splash(HZ*3, 0, true, "Out of memory");
return; /* exit */
}
rc = GetFlashInfo(&FlashInfo);
ShowFlashInfo(&FlashInfo);
if (FlashInfo.size == 0) /* no valid chip */
{
return; /* exit */
}
rb->lcd_puts_scroll(0, 0, FILENAME);
rb->lcd_puts_scroll(0, 1, "[Menu] to check");
button = WaitForButton();
if (button != BUTTON_MENU)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Checking...");
rc = CheckFirmwareFile(FILENAME, FlashInfo.size);
rb->lcd_puts(0, 0, "Checked:");
switch (rc)
{
case eOK:
rb->lcd_puts(0, 1, "File OK.");
break;
case eFileNotFound:
rb->lcd_puts_scroll(0, 0, "File not found:");
rb->lcd_puts_scroll(0, 1, FILENAME);
break;
case eTooBig:
rb->lcd_puts_scroll(0, 0, "File too big,");
rb->lcd_puts_scroll(0, 1, "larger than chip.");
break;
case eTooSmall:
rb->lcd_puts_scroll(0, 0, "File too small.");
rb->lcd_puts_scroll(0, 1, "Incomplete?");
break;
case eReadErr:
rb->lcd_puts_scroll(0, 0, "Read error.");
break;
case eBadContent:
rb->lcd_puts_scroll(0, 0, "File invalid.");
rb->lcd_puts_scroll(0, 1, "Sanity check failed.");
break;
case eCrcErr:
rb->lcd_puts_scroll(0, 0, "File invalid.");
rb->lcd_puts_scroll(0, 1, "CRC check failed.");
break;
default:
rb->lcd_puts_scroll(0, 0, "Check failed.");
break;
}
rb->sleep(HZ*3);
if (rc == eOK)
{
rb->lcd_puts_scroll(0, 0, "[On] to program,");
rb->lcd_puts_scroll(0, 1, "other key to exit.");
}
else
{ /* error occured */
return;
}
button = WaitForButton();
if (button != BUTTON_ON)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts_scroll(0, 0, "Are you sure?");
rb->lcd_puts_scroll(0, 1, "[+] to proceed.");
button = WaitForButton();
if (button != BUTTON_RIGHT)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts_scroll(0, 0, "Programming...");
rc = ProgramFirmwareFile(FILENAME, FlashInfo.size);
if (rc)
{ /* errors */
rb->lcd_clear_display();
rb->lcd_puts_scroll(0, 0, "Programming failed!");
rb->snprintf(buf, sizeof(buf), "%d errors", rc);
rb->lcd_puts_scroll(0, 1, buf);
WaitForButton();
}
rb->lcd_clear_display();
rb->lcd_puts_scroll(0, 0, "Verifying...");
rc = VerifyFirmwareFile(FILENAME);
rb->lcd_clear_display();
if (rc == 0)
{
rb->lcd_puts_scroll(0, 0, "Verify OK.");
}
else
{
rb->snprintf(buf, sizeof(buf), "Verify failed! %d errors", rc);
rb->lcd_puts_scroll(0, 0, buf);
}
rb->lcd_puts_scroll(0, 1, "Press any key to exit.");
WaitForButton();
}
#endif /* not HAVE_LCD_BITMAP */
/***************** Plugin Entry Point *****************/
enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
/* this macro should be called as the first thing you do in the plugin.
it test that the api version and model the plugin was compiled for
matches the machine it is running on */
TEST_PLUGIN_API(api);
/* if you don't use the parameter, you can do like
this to avoid the compiler warning about it */
(void)parameter;
rb = api; /* copy to global api pointer */
/* now go ahead and have fun! */
DoUserDialog();
return PLUGIN_OK;
}
#endif /* #ifndef SIMULATOR */