summaryrefslogtreecommitdiff
path: root/apps/plugins/iriver_flash.c
diff options
context:
space:
mode:
authorMiika Pekkarinen <miipekk@ihme.org>2006-08-05 16:11:39 +0000
committerMiika Pekkarinen <miipekk@ihme.org>2006-08-05 16:11:39 +0000
commit85ba65d2a3fa3d10799efadbe3a33f026bf354df (patch)
tree63fc1ab314089455d147eb99113d1e6c7b9a3a49 /apps/plugins/iriver_flash.c
parent174af79c3182e9e8b57ebb575341e85fd484b888 (diff)
downloadrockbox-85ba65d2a3fa3d10799efadbe3a33f026bf354df.zip
rockbox-85ba65d2a3fa3d10799efadbe3a33f026bf354df.tar.gz
rockbox-85ba65d2a3fa3d10799efadbe3a33f026bf354df.tar.bz2
rockbox-85ba65d2a3fa3d10799efadbe3a33f026bf354df.tar.xz
Initial version of the iriver flashing plugin for H1xx. Building of
the code not yet enabled, because the code still lacks some features and safety checks. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10463 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/iriver_flash.c')
-rw-r--r--apps/plugins/iriver_flash.c569
1 files changed, 569 insertions, 0 deletions
diff --git a/apps/plugins/iriver_flash.c b/apps/plugins/iriver_flash.c
new file mode 100644
index 0000000..28734d1
--- /dev/null
+++ b/apps/plugins/iriver_flash.c
@@ -0,0 +1,569 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * !!! DON'T MESS WITH THIS CODE UNLESS YOU'RE ABSOLUTELY SURE WHAT YOU DO !!!
+ *
+ * Copyright (C) 2006 by Miika Pekkarinen
+ *
+ * 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"
+
+/* All CFI flash routines are copied and ported from firmware_flash.c */
+
+#ifndef SIMULATOR /* only for target */
+
+unsigned char *audiobuf;
+int audiobuf_size;
+
+#if defined(IRIVER_H120)
+#define PLATFORM_ID ID_IRIVER_H100
+#else
+#undef PLATFORM_ID /* this platform is not (yet) flashable */
+#endif
+
+#ifdef PLATFORM_ID
+
+PLUGIN_HEADER
+
+#if CONFIG_KEYPAD == IRIVER_H100_PAD
+#define KEY1 BUTTON_OFF
+#define KEY2 BUTTON_ON
+#define KEY3 BUTTON_SELECT
+#define KEYNAME1 "[Stop]"
+#define KEYNAME2 "[On]"
+#define KEYNAME3 "[Select]"
+#endif
+
+struct flash_info
+{
+ uint8_t manufacturer;
+ uint8_t id;
+ int size;
+ char name[32];
+};
+
+static struct plugin_api* rb; /* here is a global api struct pointer */
+
+#ifdef IRIVER_H100_SERIES
+#define SEC_SIZE 4096
+#define BOOTLOADER_ERASEGUARD (BOOTLOADER_ENTRYPOINT / SEC_SIZE - 1)
+
+static volatile uint16_t* FB = (uint16_t*)0x00000000; /* Flash base address */
+#endif
+
+/* read the manufacturer and device ID */
+bool cfi_read_id(volatile uint16_t* pBase, uint8_t* pManufacturerID, uint8_t* pDeviceID)
+{
+ uint8_t not_manu, not_id; /* read values before switching to ID mode */
+ uint8_t manu, id; /* read values when in ID mode */
+
+ pBase = (uint16_t*)((uint32_t)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 cfi_erase_sector(volatile uint16_t* pAddr)
+{
+ unsigned timeout = 430000; /* the timeout loop should be no less than 25ms */
+
+ FB[0x5555] = 0xAA; /* enter command mode */
+ FB[0x2AAA] = 0x55;
+ FB[0x5555] = 0x80; /* erase command */
+ FB[0x5555] = 0xAA; /* enter command mode */
+ FB[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 != 0xFFFF && --timeout); /* poll for erased */
+
+ return (timeout != 0);
+}
+
+
+/* address must be in an erased location */
+inline bool cfi_program_word(volatile uint16_t* pAddr, uint16_t data)
+{
+ unsigned timeout = 85; /* 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 cfi_get_flash_info(struct flash_info* pInfo)
+{
+ rb->memset(pInfo, 0, sizeof(struct flash_info));
+
+ if (!cfi_read_id(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 if (pInfo->id == 0x82)
+ {
+ pInfo->size = 2048* 1024; /* 2 MiB */
+ rb->strcpy(pInfo->name, "SST39VF160");
+ 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;
+}
+
+
+/***************** User Interface Functions *****************/
+int WaitForButton(void)
+{
+ int button;
+
+ do
+ {
+ button = rb->button_get(true);
+ } while (button & BUTTON_REL);
+
+ return button;
+}
+
+/* helper for DoUserDialog() */
+void ShowFlashInfo(struct flash_info* 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();
+}
+
+int load_firmware_file(const char *filename, uint32_t *checksum)
+{
+ int fd;
+ int len, rc;
+ int i;
+ uint32_t sum;
+
+ fd = rb->open(filename, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ len = rb->filesize(fd);
+
+ if (audiobuf_size < len)
+ {
+ rb->splash(HZ*3, true, "Out of memory!");
+ rb->close(fd);
+ return -2;
+ }
+
+ rb->read(fd, checksum, 4);
+ rb->lseek(fd, FIRMWARE_OFFSET_FILE_DATA, SEEK_SET);
+ len -= FIRMWARE_OFFSET_FILE_DATA;
+
+ rc = rb->read(fd, audiobuf, len);
+ rb->close(fd);
+ if (rc != len)
+ {
+ rb->splash(HZ*3, true, "Read failure");
+ return -3;
+ }
+
+ /* Verify the checksum */
+ sum = 0;
+ for (i = 0; i < len; i++)
+ sum += audiobuf[i];
+
+ if (sum != *checksum)
+ {
+ rb->splash(HZ*3, true, "Checksums mismatch!");
+ return -4;
+ }
+
+ return len;
+}
+
+int flash_rockbox(const char *filename)
+{
+ struct flash_header hdr;
+ char buf[32];
+ int pos, i, len, rc;
+ unsigned long checksum, sum;
+ unsigned char *p8;
+ uint16_t *p16;
+
+ len = load_firmware_file(filename, &checksum);
+ if (len < 0)
+ return len * 10;
+
+ /* Erase the program flash. */
+ for (i = 1; i < BOOTLOADER_ERASEGUARD && (i-1)*4096 < len + 32; i++)
+ {
+ rc = cfi_erase_sector(FB + (SEC_SIZE/2) * i);
+ rb->snprintf(buf, sizeof(buf), "Erase: 0x%03x (%d)", i, rc);
+ rb->lcd_puts(0, 3, buf);
+ rb->lcd_update();
+ }
+
+ /* Write the magic and size. */
+ rb->memset(&hdr, 0, sizeof(struct flash_header));
+ hdr.magic = FLASH_MAGIC;
+ hdr.length = len;
+ // rb->strncpy(hdr.version, APPSVERSION, sizeof(hdr.version)-1);
+ p16 = (uint16_t *)&hdr;
+
+ rb->snprintf(buf, sizeof(buf), "Programming");
+ rb->lcd_puts(0, 4, buf);
+ rb->lcd_update();
+
+ pos = FLASH_ENTRYPOINT/2;
+ for (i = 0; i < (long)sizeof(struct flash_header)/2; i++)
+ {
+ cfi_program_word(FB + pos, p16[i]);
+ pos++;
+ }
+
+ p16 = (uint16_t *)audiobuf;
+ for (i = 0; i < len/2 && pos < (BOOTLOADER_ENTRYPOINT/2); i++)
+ cfi_program_word(FB + pos + i, p16[i]);
+
+ /* Verify */
+ p8 = (char *)FLASH_ENTRYPOINT;
+ p8 += sizeof(struct flash_header);
+ sum = 0;
+ for (i = 0; i < len; i++)
+ sum += p8[i];
+
+ if (sum != checksum)
+ {
+ rb->splash(HZ*3, true, "Verify failed!");
+ /* Erase the magic sector so bootloader does not try to load
+ * rockbox from flash and crash. */
+ cfi_erase_sector(FB + SEC_SIZE/2);
+ return -5;
+ }
+
+ return 0;
+}
+
+void show_fatal_error(void)
+{
+ rb->splash(HZ*30, true, "Disable idle poweroff, connect AC power and DON'T TURN PLAYER OFF!!");
+ rb->splash(HZ*30, true, "Contact Rockbox developers as soon as possible!");
+ rb->splash(HZ*30, true, "Your device won't be bricked unless you turn off the power");
+ rb->splash(HZ*30, true, "Don't use the device before further instructions from Rockbox developers");
+}
+
+int flash_bootloader(const char *filename)
+{
+ // char buf[32];
+ int pos, i, len, rc;
+ unsigned long checksum, sum, crc32;
+ unsigned char *p8;
+ uint16_t *p16;
+
+ len = load_firmware_file(filename, &checksum);
+ if (len < 0)
+ return len * 10;
+
+ if (len > 0xFFFF)
+ {
+ rb->splash(HZ*3, true, "Too big bootloader");
+ return -1;
+ }
+
+ /* Verify the crc32 checksum also. */
+ crc32 = crc_32(audiobuf, len, 0xffffffff);
+ // rb->snprintf(buf, sizeof buf, "crc32 = 0x%08x", crc32);
+ // rb->splash(HZ*10, true, buf);
+
+ if (crc32 != 0x5361a679)
+ {
+ rb->splash(HZ*3, true, "Untested bootloader");
+ return -2;
+ }
+
+ rb->lcd_puts(0, 3, "Processing critical sections...");
+ rb->lcd_update();
+
+ /* Erase the boot sector and write a proper reset vector. */
+ cfi_erase_sector(FB);
+ p16 = (uint16_t *)audiobuf;
+ for (i = 0; i < 4; i++)
+ cfi_program_word(FB + i, p16[i]);
+
+ /* Erase the bootloader flash section. */
+ for (i = BOOTLOADER_ENTRYPOINT/SEC_SIZE; i < 0x200; i++)
+ rc = cfi_erase_sector(FB + (SEC_SIZE/2) * i);
+
+ pos = BOOTLOADER_ENTRYPOINT/2;
+ p16 = (uint16_t *)audiobuf;
+ for (i = 0; i < len/2; i++)
+ cfi_program_word(FB + pos + i, p16[i]);
+
+ /* Verify */
+ p8 = (char *)BOOTLOADER_ENTRYPOINT;
+ sum = 0;
+ for (i = 0; i < len; i++)
+ sum += p8[i];
+
+ if (sum != checksum)
+ {
+ rb->splash(HZ*3, true, "Verify failed!");
+ show_fatal_error();
+ return -5;
+ }
+
+ p8 = (char *)FB;
+ for (i = 0; i < 8; i++)
+ {
+ if (p8[i] != audiobuf[i])
+ {
+ rb->splash(HZ*3, true, "Bootvector corrupt!");
+ show_fatal_error();
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* Kind of our main function, defines the application flow. */
+void DoUserDialog(char* filename)
+{
+ struct flash_info fi;
+ int rc; /* generic return code */
+
+ /* this can only work if Rockbox runs in DRAM, not flash ROM */
+ if ((uint16_t*)rb >= FB && (uint16_t*)rb < FB + 4096*1024) /* 4 MB max */
+ { /* we're running from flash */
+ rb->splash(HZ*3, true, "Not from ROM");
+ return; /* exit */
+ }
+
+ /* refuse to work if the power may fail meanwhile */
+ if (!rb->battery_level_safe())
+ {
+ rb->splash(HZ*3, true, "Battery too low!");
+ return; /* exit */
+ }
+
+ rb->lcd_setfont(FONT_SYSFIXED);
+
+ rc = cfi_get_flash_info(&fi);
+ ShowFlashInfo(&fi);
+ if (fi.size == 0) /* no valid chip */
+ {
+ rb->splash(HZ*3, true, "Sorry!");
+ return; /* exit */
+ }
+
+ /* Debug? */
+#if 0
+ rb->memcpy(&hdr, (uint8_t *)(FLASH_ENTRYPOINT), sizeof(struct flash_header));
+ rb->snprintf(buf, sizeof(buf), "Magic: 0x%03x", hdr.magic);
+ rb->lcd_puts(0, 3, buf);
+ rb->snprintf(buf, sizeof(buf), "Size: 0x%03x", hdr.length);
+ rb->lcd_puts(0, 4, buf);
+ rb->lcd_update();
+ rb->sleep(HZ*10);
+
+ rb->memcpy(&hdr, (uint8_t *)(FLASH_ENTRYPOINT/2), sizeof(struct flash_header));
+ rb->snprintf(buf, sizeof(buf), "Magic: 0x%03x", hdr.magic);
+ rb->lcd_puts(0, 3, buf);
+ rb->snprintf(buf, sizeof(buf), "Size: 0x%03x", hdr.length);
+ rb->lcd_puts(0, 4, buf);
+ rb->lcd_update();
+ rb->sleep(HZ*10);
+#endif
+
+ /* Restore? */
+#if 0
+ fd = rb->open("/internal_rom_000000-1FFFFF.bin", O_RDONLY);
+ if (fd < 0)
+ return ;
+ len = rb->filesize(fd);
+
+ /* Erase the program flash. */
+ for (i = 1; i < 0x1EF; i++)
+ {
+ rc = cfi_erase_sector(FB + (SEC_SIZE/2) * i);
+ rb->snprintf(buf, sizeof(buf), "Erase: 0x%03x (%d)", i, rc);
+ rb->lcd_puts(0, 3, buf);
+ rb->lcd_update();
+ }
+
+ i = FLASH_ENTRYPOINT/2;
+ rb->lseek(fd, i*2, SEEK_SET);
+ len -= i*2 - 0xffff;
+ for (; len > 0 && i < (0x1F0000/2); i++)
+ {
+ rb->read(fd, bytes, 2);
+ cfi_program_word(FB + i, (bytes[0] << 8) | bytes[1]);
+ len -= 2;
+ }
+
+ rb->close(fd);
+ return ;
+#endif
+
+ if (filename == NULL)
+ {
+ rb->splash(HZ*3, true, "Please use this plugin with \"Open with...\"");
+ return ;
+ }
+
+ audiobuf = rb->plugin_get_audio_buffer(&audiobuf_size);
+
+ if (rb->strcasestr(filename, "/rockbox.iriver"))
+ flash_rockbox(filename);
+ else if (rb->strcasestr(filename, "/bootloader.iriver"))
+ flash_bootloader(filename);
+ else
+ rb->splash(HZ*3, true, "Unknown file type");
+}
+
+
+/***************** Plugin Entry Point *****************/
+
+enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
+{
+ int oldmode;
+
+ rb = api; /* copy to global api pointer */
+
+ /* now go ahead and have fun! */
+ oldmode = rb->system_memory_guard(MEMGUARD_NONE); /*disable memory guard */
+ DoUserDialog((char*) parameter);
+ rb->system_memory_guard(oldmode); /* re-enable memory guard */
+
+ return PLUGIN_OK;
+}
+
+#endif /* ifdef PLATFORM_ID */
+#endif /* #ifndef SIMULATOR */