summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/plugins/rockbox_flash.c664
1 files changed, 664 insertions, 0 deletions
diff --git a/apps/plugins/rockbox_flash.c b/apps/plugins/rockbox_flash.c
new file mode 100644
index 0000000000..829cc5a346
--- /dev/null
+++ b/apps/plugins/rockbox_flash.c
@@ -0,0 +1,664 @@
+/***************************************************************************
+* __________ __ ___.
+* Open \______ \ ____ ____ | | _\_ |__ _______ ___
+* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+* \/ \/ \/ \/ \/
+* $Id$
+*
+* Plugin for reprogramming only the second Rockbox image.
+*
+* 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 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 (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;
+}