From 85ba65d2a3fa3d10799efadbe3a33f026bf354df Mon Sep 17 00:00:00 2001 From: Miika Pekkarinen Date: Sat, 5 Aug 2006 16:11:39 +0000 Subject: 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 --- apps/plugins/iriver_flash.c | 569 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 apps/plugins/iriver_flash.c (limited to 'apps/plugins/iriver_flash.c') diff --git a/apps/plugins/iriver_flash.c b/apps/plugins/iriver_flash.c new file mode 100644 index 0000000000..28734d197b --- /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 */ -- cgit v1.2.3