From 6fad145b9ecd6d5713b51ad3372597aee4d14c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohensohn?= Date: Sun, 13 Jul 2003 22:57:13 +0000 Subject: [PATCH] The plugin which reflashes the second image. I release it now to become part of the build, so that I don't have to maintain and distribute it every time the plugin API version is bumped. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3832 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/rockbox_flash.c | 664 +++++++++++++++++++++++++++++++++++ 1 file changed, 664 insertions(+) create mode 100644 apps/plugins/rockbox_flash.c 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; iread(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; iread(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; +}