rockbox/apps/plugins/rockbox_flash.c

670 lines
16 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Plugin for reprogramming only the second Rockbox image.
*
* Copyright (C) 2003 J<EFBFBD>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"
/* Only build for target */
#ifndef SIMULATOR
#ifndef UINT8
#define UINT8 unsigned char
#endif
#ifndef UINT16
#define UINT16 unsigned short
#endif
#ifndef UINT32
#define UINT32 unsigned long
#endif
// hard-coded values
static volatile UINT8* FB = (UINT8*)0x02000000; // Flash base address
#define SECTORSIZE 4096 // size of one flash sector
#define ROCKBOX_DEST 0x09000000
#define ROCKBOX_EXEC 0x09000200
#define FILENAME "/rockbox.ucl"
#define VERS_ADR 0xFE // position of firmware version value in Flash
#define UCL_HEADER 26 // size of the header generated by uclpack
typedef struct
{
UINT32 destination; // address to copy it to
UINT32 size; // how many bytes of payload (to the next header)
UINT32 execute; // entry point
UINT32 flags; // uncompressed or compressed
// end of header, now comes the payload
} tImageHeader;
// result of the CheckFirmwareFile() function
typedef enum
{
eOK = 0,
eFileNotFound, // errors from here on
eTooBig,
eTooSmall,
eReadErr,
eNotUCL,
eWrongAlgorithm,
eMultiBlocks,
} 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 SEC_SIZE 4096 // size of one flash sector
static UINT8 sector[SEC_SIZE]; // better not place this on the stack...
/***************** 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); // round down to 512k align, to make shure
not_manu = pBase[0]; // read the normal content
not_id = pBase[1]; // should be 'A' (0x41) and 'R' (0x52) 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
}
// eraze the sector which contains the given address
bool ErazeSector(volatile UINT8* pAddr)
{
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; // eraze command
pBase[0x5555] = 0xAA; // enter command mode
pBase[0x2AAA] = 0x55;
*pAddr = 0x30; // eraze 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 erazed
return (timeout != 0);
}
// address must be in an erazed location
inline bool ProgramByte(volatile UINT8* pAddr, UINT8 data)
{
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);
}
// 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;
}
/*********** Tool Functions ************/
// place a 32 bit value into memory, big endian
void Write32(UINT8* pByte, UINT32 value)
{
pByte[0] = (UINT8)(value >> 24);
pByte[1] = (UINT8)(value >> 16);
pByte[2] = (UINT8)(value >> 8);
pByte[3] = (UINT8)(value);
}
// read a 32 bit value from memory, big endian
UINT32 Read32(UINT8* pByte)
{
UINT32 value = 0;
value |= (UINT32)pByte[0] << 24;
value |= (UINT32)pByte[1] << 16;
value |= (UINT32)pByte[2] << 8;
value |= (UINT32)pByte[3];
return value;
}
// get the start address of the second image
tImageHeader* GetSecondImage(void)
{
tImageHeader* pImage1;
UINT32 pos = 0; // dafault: not found
UINT32* pFlash = (UINT32*)FB;
UINT16 version = *(UINT16*)(FB + VERS_ADR);
if (version < 200) // at least 2.00
{
return 0; // not our flash layout
}
// determine the first image position
pos = pFlash[2] + pFlash[3]; // position + size of the bootloader = after it
pos = (pos + 3) & ~3; // be shure it's 32 bit aligned
pImage1 = (tImageHeader*)pos;
if (pImage1->destination != ROCKBOX_DEST || pImage1->execute != ROCKBOX_EXEC)
return 0; // seems to be no Rockbox stuff in here
if (pImage1->size != 0)
{ // success, we have a second image
pos = (UINT32)pImage1 + sizeof(tImageHeader) + pImage1->size;
if (((pos + SECTORSIZE-1) & ~(SECTORSIZE-1)) != pos)
{ // not sector-aligned
pos = 0; // sanity check failed
}
}
return (tImageHeader*)pos;
}
/*********** Image File Functions ************/
// so far, only compressed images in UCL NRV algorithm 2e supported
tCheckResult CheckImageFile(char* filename, int space, tImageHeader* pHeader)
{
int i;
int fd;
int filesize; // size info
int fileread = 0; // total size as read from the file
int read; // how many for this sector
// magic file header for compressed files
static const UINT8 magic[8] = { 0x00,0xe9,0x55,0x43,0x4c,0xff,0x01,0x1a };
UINT8 ucl_header[UCL_HEADER];
fd = rb->open(filename, O_RDONLY);
if (fd < 0)
return eFileNotFound;
filesize = rb->filesize(fd);
if (filesize - (int)sizeof(ucl_header) - 8 > space)
{
rb->close(fd);
return eTooBig;
}
else if (filesize < 50000) // give it some reasonable lower limit
{
rb->close(fd);
return eTooSmall;
}
// do some sanity checks
read = rb->read(fd, ucl_header, sizeof(ucl_header));
fileread += read;
if (read != sizeof(ucl_header))
{
rb->close(fd);
return eReadErr;
}
// compare the magic header
for (i=0; i<8; i++)
{
if (ucl_header[i] != magic[i])
{
rb->close(fd);
return eNotUCL;
}
}
// check for supported algorithm
if (ucl_header[12] != 0x2E)
{
rb->close(fd);
return eWrongAlgorithm;
}
pHeader->size = Read32(ucl_header + 22); // compressed size
if (pHeader->size != filesize - sizeof(ucl_header) - 8)
{
rb->close(fd);
return eMultiBlocks;
}
if (Read32(ucl_header + 18) > pHeader->size) // compare with uncompressed size
{ // normal case
pHeader->flags = 0x00000001; // flags for UCL compressed
}
else
{
pHeader->flags = 0x00000000; // very unlikely, content was not compressible
}
// check if we can read the whole file
do
{
read = rb->read(fd, sector, SEC_SIZE);
fileread += read;
} while (read == SEC_SIZE);
rb->close(fd);
if (fileread != filesize)
return eReadErr;
// fill in the hardcoded rest of the header
pHeader->destination = ROCKBOX_DEST;
pHeader->execute = ROCKBOX_EXEC;
return eOK;
}
// returns the # of failures, 0 on success
unsigned ProgramImageFile(char* filename, UINT8* pos, tImageHeader* pImageHeader, int start, int size)
{
int i;
int fd;
int read; // how many for this sector
unsigned failures = 0;
fd = rb->open(filename, O_RDONLY);
if (fd < 0)
return false;
// no error checking necessary here, we checked for minimum size already
rb->lseek(fd, start, SEEK_SET); // go to start position
*(tImageHeader*)sector = *pImageHeader; // copy header into sector buffer
read = rb->read(fd, sector + sizeof(tImageHeader), SEC_SIZE - sizeof(tImageHeader)); // payload behind
size -= read;
read += sizeof(tImageHeader); // to be programmed, but not part of the file
do
{
if (!ErazeSector(pos))
{
// nothing we can do, let the programming count the errors
}
for (i=0; i<read; i++)
{
if (!ProgramByte(pos + i, sector[i]))
{
failures++;
}
}
pos += SEC_SIZE;
read = rb->read(fd, sector, (size > SEC_SIZE) ? SEC_SIZE : size); // payload for next sector
size -= read;
} while (read > 0);
rb->close(fd);
return failures;
}
// returns the # of failures, 0 on success
unsigned VerifyImageFile(char* filename, UINT8* pos, tImageHeader* pImageHeader, int start, int size)
{
int i;
int fd;
int read; // how many for this sector
unsigned failures = 0;
fd = rb->open(filename, O_RDONLY);
if (fd < 0)
return false;
// no error checking necessary here, we checked for minimum size already
rb->lseek(fd, start, SEEK_SET); // go to start position
*(tImageHeader*)sector = *pImageHeader; // copy header into sector buffer
read = rb->read(fd, sector + sizeof(tImageHeader), SEC_SIZE - sizeof(tImageHeader)); // payload behind
size -= read;
read += sizeof(tImageHeader); // to be programmed, but not part of the file
do
{
for (i=0; i<read; i++)
{
if (pos[i] != sector[i])
{
failures++;
}
}
pos += SEC_SIZE;
read = rb->read(fd, sector, (size > SEC_SIZE) ? SEC_SIZE : size); // payload for next sector
size -= read;
} while (read);
rb->close(fd);
return failures;
}
/***************** User Interface Functions *****************/
/* (to be changed for Player) */
#ifdef HAVE_LCD_BITMAP
// helper for DoUserDialog()
void ShowFlashInfo(tFlashInfo* pInfo, tImageHeader* pImageHeader)
{
char buf[32];
if (!pInfo->manufacturer)
{
rb->lcd_puts(0, 0, "Flash: M=?? D=??");
}
else
{
if (pInfo->size)
{
rb->snprintf(buf, sizeof(buf), "Flash size: %d KB", pInfo->size / 1024);
rb->lcd_puts(0, 0, buf);
}
else
{
rb->lcd_puts(0, 0, "Unsupported chip");
}
}
if (pImageHeader)
{
rb->snprintf(buf, sizeof(buf), "Image at %d KB", ((UINT8*)pImageHeader - FB) / 1024);
rb->lcd_puts(0, 1, buf);
}
else
{
rb->lcd_puts(0, 1, "No image found!");
}
rb->lcd_update();
}
// Kind of our main function, defines the application flow.
void DoUserDialog(void)
{
tImageHeader ImageHeader;
tFlashInfo FlashInfo;
char buf[32];
int button;
int rc; // generic return code
UINT32 space, aligned_size, true_size;
UINT8* pos;
rb->lcd_setfont(FONT_SYSFIXED);
pos = (void*)GetSecondImage();
rc = GetFlashInfo(&FlashInfo);
ShowFlashInfo(&FlashInfo, (void*)pos);
if (FlashInfo.size == 0) // no valid chip
{
rb->splash(HZ*3, 0, true, "Not flashable");
return; // exit
}
else if (pos == 0)
{
rb->splash(HZ*3, 0, true, "No Image");
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 = rb->button_get(true);
button = rb->button_get(true);
if (button != BUTTON_F1)
{
return;
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "checking...");
rb->lcd_update();
space = FlashInfo.size - (pos-FB + sizeof(ImageHeader)); // size minus start
rc = CheckImageFile(FILENAME, space, &ImageHeader);
rb->lcd_puts(0, 0, "checked:");
switch (rc)
{
case eOK:
rb->lcd_puts(0, 1, "File OK.");
break;
case eNotUCL:
rb->lcd_puts(0, 1, "File not UCL ");
rb->lcd_puts(0, 2, "compressed.");
rb->lcd_puts(0, 3, "Use uclpack --2e");
rb->lcd_puts(0, 4, " --10 rockbox.bin");
break;
case eWrongAlgorithm:
rb->lcd_puts(0, 1, "Wrong algorithm");
rb->lcd_puts(0, 2, "for compression.");
rb->lcd_puts(0, 3, "Use uclpack --2e");
rb->lcd_puts(0, 4, " --10 rockbox.bin");
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, "won't fit in 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, "File read error.");
break;
case eMultiBlocks:
rb->lcd_puts(0, 1, "File invalid.");
rb->lcd_puts(0, 2, "Blocksize");
rb->lcd_puts(0, 3, " too small?");
break;
default:
rb->lcd_puts(0, 1, "Check failed.");
break;
}
if (rc == eOK)
{ // was OK
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 = rb->button_get(true);
button = rb->button_get(true);
if (rc != eOK || button != BUTTON_F2)
{
return;
}
true_size = ImageHeader.size;
aligned_size = ((sizeof(tImageHeader) + true_size + SECTORSIZE-1) & ~(SECTORSIZE-1)) - sizeof(tImageHeader); // round up to next flash sector
ImageHeader.size = aligned_size; // increase image size such that we reach the next sector
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Programming...");
rb->lcd_update();
rc = ProgramImageFile(FILENAME, pos, &ImageHeader, UCL_HEADER, true_size);
if (rc)
{ // errors
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Error:");
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 = rb->button_get(true);
button = rb->button_get(true);
}
rb->lcd_clear_display();
rb->lcd_puts(0, 0, "Verifying...");
rb->lcd_update();
rc = VerifyImageFile(FILENAME, pos, &ImageHeader, UCL_HEADER, true_size);
rb->lcd_clear_display();
if (rc == 0)
{
rb->lcd_puts(0, 0, "Verify OK.");
}
else
{
rb->lcd_puts(0, 0, "Error:");
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, 3, "Use safe image");
rb->lcd_puts(0, 4, "if booting hangs:");
rb->lcd_puts(0, 5, "F1 during power-on");
}
rb->lcd_puts(0, 7, "Any key to exit");
rb->lcd_update();
button = rb->button_get(true);
button = rb->button_get(true);
}
#else // HAVE_LCD_BITMAP
// Player implementation has to go here
void DoUserDialog(void)
{
}
#endif // 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