/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * !!! DON'T MESS WITH THIS CODE UNLESS YOU'RE ABSOLUTELY SURE WHAT YOU DO !!! * * Copyright (C) 2020 by James Buren (refactor + H300 support) * Copyright (C) 2006 by Miika Pekkarinen (original + H100/H120 support) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" #include "lib/helper.h" #include "checksum.h" /* * Flash commands may rely on null pointer dereferences to work correctly. * Disable this feature of GCC that may interfere with proper code generation. */ #pragma GCC optimize "no-delete-null-pointer-checks" enum firmware { FIRMWARE_ROCKBOX, /* all .iriver firmwares */ FIRMWARE_ROMDUMP, /* a debug romdump */ FIRMWARE_ORIGINAL, /* an unscrambled original firmware */ }; #define WORD_SIZE 2 #define BOOT_VECTOR_SIZE 8 #define BOOT_SECTOR_OFFSET 0 #define SECTOR_SIZE 4096 #define BOOTLOADER_MAX_SIZE 65536 #define BOOTLOADER_SECTORS (BOOTLOADER_MAX_SIZE / SECTOR_SIZE) #define RAM_IMAGE_RAW_SIZE (FLASH_ROMIMAGE_ENTRY - FLASH_RAMIMAGE_ENTRY) #define RAM_IMAGE_MAX_SIZE (RAM_IMAGE_RAW_SIZE - sizeof(struct flash_header)) #define RAM_IMAGE_SECTORS (RAM_IMAGE_RAW_SIZE / SECTOR_SIZE) #define ROM_IMAGE_RAW_SIZE (BOOTLOADER_ENTRYPOINT - FLASH_ROMIMAGE_ENTRY) #define ROM_IMAGE_MAX_SIZE (ROM_IMAGE_RAW_SIZE - sizeof(struct flash_header)) #define ROM_IMAGE_SECTORS (ROM_IMAGE_RAW_SIZE / SECTOR_SIZE) #define ROM_IMAGE_RELOCATION (FLASH_ROMIMAGE_ENTRY + sizeof(struct flash_header)) #define WHOLE_FIRMWARE_SECTORS (BOOTLOADER_ENTRYPOINT / SECTOR_SIZE) #define FIRMWARE_OFFSET 544 #if BOOTLOADER_ENTRYPOINT + BOOTLOADER_MAX_SIZE != FLASH_SIZE #error "Bootloader is not located at the end of flash." #endif #if FLASH_ROMIMAGE_ENTRY < FLASH_RAMIMAGE_ENTRY #error "RAM image must be located before the ROM image." #endif #if BOOTLOADER_ENTRYPOINT < FLASH_ROMIMAGE_ENTRY #error "ROM image must be located before the bootloader." #endif #if FLASH_SIZE == 2048 * 1024 #define ROMDUMP "/internal_rom_000000-1FFFFF.bin" #elif FLASH_SIZE == 4096 * 1024 #define ROMDUMP "/internal_rom_000000-3FFFFF.bin" #endif #ifdef IRIVER_H100 #define MODEL (const uint8_t[]) { 'h', '1', '0', '0' } #define ORIGINAL "/ihp_100.bin" #elif defined(IRIVER_H120) #define MODEL (const uint8_t[]) { 'h', '1', '2', '0' } #define ORIGINAL "/ihp_120.bin" #elif defined(IRIVER_H300) #define MODEL (const uint8_t[]) { 'h', '3', '0', '0' } #define ORIGINAL "/H300.bin" #else #error "Unsupported target." #endif struct flash_info { uint16_t vendor; uint16_t product; uint32_t size; char name[16]; }; /* checks if the region has a valid bootloader */ static bool detect_valid_bootloader(const void* ptr, uint32_t size) { static const struct { uint32_t size; uint32_t crc32; } bootloaders[] = { #ifdef IRIVER_H100 { 53556, 0x76541ebd }, /* 8 */ #elif defined(IRIVER_H120) { 53556, 0xd262b12b }, /* 8 */ #elif defined(IRIVER_H300) { 57048, 0x59ba2459 }, /* 8 */ #endif {0} }; for (size_t i = 0; bootloaders[i].size != 0; i++) { uint32_t crc32; if (size != 0 && size != bootloaders[i].size) continue; crc32 = rb->crc_32(ptr, bootloaders[i].size, 0xFFFFFFFF); if (crc32 == bootloaders[i].crc32) return true; } return false; } /* get read-only access to flash at the given offset */ static const void* flash(uint32_t offset) { const uint16_t* FB = (uint16_t*) FLASH_BASE; return &FB[offset / WORD_SIZE]; } /* queries the rom for information and returns it if it is known */ static bool flash_get_info(const struct flash_info** out_info) { static const struct flash_info roms[] = { #if FLASH_SIZE == 2048 * 1024 { 0x00BF, 0x2782, 2048 * 1024, "SST39VF160" }, #elif FLASH_SIZE == 4096 * 1024 { 0x00BF, 0x235B, 4096 * 1024, "SST39VF3201" }, #else #error "Unsupported rom chip." #endif {0} }; static struct flash_info unknown_rom = {0}; volatile uint16_t* FB = (uint16_t*) FLASH_BASE; uint16_t vendor; uint16_t product; /* execute the software ID entry command */ FB[0x5555] = 0xAA; FB[0x2AAA] = 0x55; FB[0x5555] = 0x90; rb->sleep(HZ / 100); /* copy the IDs from the previous command */ vendor = FB[0]; product = FB[1]; /* execute the software ID exit command */ FB[0x5555] = 0xAA; FB[0x2AAA] = 0x55; FB[0x5555] = 0xF0; rb->sleep(HZ / 100); /* search for a known match */ for (size_t i = 0; roms[i].size != 0; i++) { if (roms[i].vendor == vendor && roms[i].product == product) { *out_info = &roms[i]; return true; } } /* return only the vendor / product ids if unknown */ unknown_rom.vendor = vendor; unknown_rom.product = product; *out_info = &unknown_rom; return false; } /* wait until the rom signals completion of an operation */ static bool flash_wait_for_rom(uint32_t offset) { const size_t MAX_TIMEOUT = 0xFFFFFF; /* should be sufficient for most targets */ const size_t RECOVERY_TIME = 64; /* based on 140MHz MCF 5249 */ volatile uint16_t* FB = (uint16_t*) FLASH_BASE; uint16_t old_data = FB[offset / WORD_SIZE] & 0x0040; /* we only want DQ6 */ volatile size_t i; /* disables certain optimizations */ bool result; /* repeat up to MAX_TIMEOUT times or until DQ6 stops flipping */ for (i = 0; i < MAX_TIMEOUT; i++) { uint16_t new_data = FB[offset / WORD_SIZE] & 0x0040; /* we only want DQ6 */ if (old_data == new_data) break; old_data = new_data; } result = i != MAX_TIMEOUT; /* delay at least 1us to give the bus time to recover */ for (i = 0; i < RECOVERY_TIME; i++); return result; } /* erase the sector at the given offset */ static bool flash_erase_sector(uint32_t offset) { volatile uint16_t* FB = (uint16_t*) FLASH_BASE; /* execute the sector erase command */ FB[0x5555] = 0xAA; FB[0x2AAA] = 0x55; FB[0x5555] = 0x80; FB[0x5555] = 0xAA; FB[0x2AAA] = 0x55; FB[offset / WORD_SIZE] = 0x30; return flash_wait_for_rom(offset); } /* program a word at the given offset */ static bool flash_program_word(uint32_t offset, uint16_t word) { volatile uint16_t* FB = (uint16_t*) FLASH_BASE; /* execute the word program command */ FB[0x5555] = 0xAA; FB[0x2AAA] = 0x55; FB[0x5555] = 0xA0; FB[offset / WORD_SIZE] = word; return flash_wait_for_rom(offset); } /* bulk erase of adjacent sectors */ static void flash_erase_sectors(uint32_t offset, uint32_t sectors, bool progress) { for (uint32_t i = 0; i < sectors; i++) { flash_erase_sector(offset + i * SECTOR_SIZE); /* display a progress report if requested */ if (progress) { rb->lcd_putsf(0, 3, "Erasing... %u%%", (i + 1) * 100 / sectors); rb->lcd_update(); } } } /* bulk program of bytes */ static void flash_program_bytes(uint32_t offset, const void* ptr, uint32_t len, bool progress) { const uint8_t* data = ptr; for (uint32_t i = 0; i < len; i += WORD_SIZE) { uint32_t j = i + 1; uint32_t k = ((j < len) ? j : i) + 1; uint16_t word = (data[i] << 8) | (j < len ? data[j] : 0xFF); flash_program_word(offset + i, word); /* display a progress report if requested */ if (progress && ((i % SECTOR_SIZE) == 0 || k == len)) { rb->lcd_putsf(0, 4, "Programming... %u%%", k * 100 / len); rb->lcd_update(); } } } /* bulk verify of programmed bytes */ static bool flash_verify_bytes(uint32_t offset, const void* ptr, uint32_t len, bool progress) { const uint8_t* FB = flash(offset); const uint8_t* data = ptr; /* don't use memcmp so we can provide progress updates */ for (uint32_t i = 0; i < len; i++) { uint32_t j = i + 1; if (FB[i] != data[i]) return false; /* display a progress report if requested */ if (progress && ((i % SECTOR_SIZE) == 0 || j == len)) { rb->lcd_putsf(0, 5, "Verifying... %u%%", j * 100 / len); rb->lcd_update(); } } return true; } /* print information about the flash chip */ static bool show_info(void) { static const struct flash_info* fi = NULL; rb->lcd_clear_display(); if (fi == NULL) flash_get_info(&fi); rb->lcd_putsf(0, 0, "Flash: V=%04x P=%04x", fi->vendor, fi->product); if (fi->size != 0) { rb->lcd_puts(0, 1, fi->name); rb->lcd_putsf(0, 2, "Size: %u KB", fi->size / 1024); } else { rb->lcd_puts(0, 1, "Unknown chip"); } rb->lcd_update(); if (fi->size == 0) { rb->splash(HZ * 3, "Sorry!"); return false; } return true; } /* confirm a user's choice */ static bool confirm_choice(const char* msg) { long button; rb->splashf(0, "%s ([PLAY] to CONFIRM)", msg); do button = rb->button_get(true); while (IS_SYSEVENT(button) || (button & BUTTON_REL)); show_info(); return (button == BUTTON_ON); } /* all-in-one firmware loader */ static bool load_firmware(const char* filename, enum firmware firmware, const void** data, size_t* data_len) { bool result = false; const char* msg = NULL; int fd = -1; off_t fd_len; uint8_t* buffer; size_t buffer_len; fd = rb->open(filename, O_RDONLY); if (fd < 0) { msg = "Aborting: open failure"; goto bail; } /* get file and buffer lengths and acquire the buffer */ fd_len = rb->filesize(fd); buffer = rb->plugin_get_audio_buffer(&buffer_len); /* ensure there's enough space in the buffer */ if ((size_t) fd_len > buffer_len) { msg = "Aborting: out of memory"; goto bail; } /* all known firmwares are less than or equal to FLASH_SIZE */ if (fd_len > FLASH_SIZE) { msg = "Aborting: firmware too big"; goto bail; } /* rockbox firmware specific code */ if (firmware == FIRMWARE_ROCKBOX) { uint32_t checksum; uint8_t model[4]; /* subtract the header length */ fd_len -= sizeof(checksum) + sizeof(model); /* sanity check the length */ if (fd_len < WORD_SIZE) { msg = "Aborting: firmware too small"; goto bail; } /* read the various parts */ if ( rb->read(fd, &checksum, sizeof(checksum)) != sizeof(checksum) || rb->read(fd, model, sizeof(model)) != sizeof(model) || rb->read(fd, buffer, fd_len) != fd_len ) { msg = "Aborting: read failure"; goto bail; } /* verify the checksum */ if (!verify_checksum(checksum, buffer, fd_len)) { msg = "Aborting: checksum mismatch"; goto bail; } /* verify the model */ if (rb->memcmp(model, MODEL, sizeof(model)) != 0) { msg = "Aborting: model mismatch"; goto bail; } } /* romdump specific code */ if (firmware == FIRMWARE_ROMDUMP) { /* the romdump should be exactly the same size as the flash */ if (fd_len != FLASH_SIZE) { msg = "Aborting: firmware size incorrect"; goto bail; } /* exclude boot vector and boot loader regions */ fd_len = BOOTLOADER_ENTRYPOINT - BOOT_VECTOR_SIZE; /* skip the boot vector */ if (rb->lseek(fd, BOOT_VECTOR_SIZE, SEEK_SET) != BOOT_VECTOR_SIZE) { msg = "Aborting: lseek failure"; goto bail; } /* read everything up to the boot loader */ if (rb->read(fd, buffer, fd_len) != fd_len) { msg = "Aborting: read failure"; goto bail; } } /* original firmware specific code */ if (firmware == FIRMWARE_ORIGINAL) { uint32_t boot_vector[2]; /* subtract the offset and the size of the boot vector */ fd_len -= FIRMWARE_OFFSET + sizeof(boot_vector); /* sanity check the length */ if (fd_len < WORD_SIZE) { msg = "Aborting: firmware too small"; goto bail; } /* skip the leading bytes, whatever they are */ if (rb->lseek(fd, FIRMWARE_OFFSET, SEEK_SET) != FIRMWARE_OFFSET) { msg = "Aborting: lseek failure"; goto bail; } /* read the various parts */ if ( rb->read(fd, boot_vector, sizeof(boot_vector)) != sizeof(boot_vector) || rb->read(fd, buffer, fd_len) != fd_len ) { msg = "Aborting: read failure"; goto bail; } /* verify the boot vector */ if (boot_vector[0] != 0x10017ff0 || boot_vector[1] != 0x00000008) { msg = "Aborting: not an original firmware"; goto bail; } } /* write the resulting buffer and length in the output parameters */ *data = buffer; *data_len = fd_len; /* mark success */ result = true; bail: /* common exit code */ if (fd >= 0) rb->close(fd); if (msg != NULL) rb->splash(HZ * 3, msg); return result; } /* prints fatal error if a critical failure occurs */ static void show_fatal_error(void) { rb->splash(HZ * 30, "Disable idle poweroff, connect AC power and DON'T TURN PLAYER OFF!"); rb->splash(HZ * 30, "Contact Rockbox developers as soon as possible!"); rb->splash(HZ * 30, "Your device won't be bricked unless you turn off the power!"); rb->splash(HZ * 30, "Don't use the device before further instructions from Rockbox developers!"); } /* flash a bootloader */ static bool flash_bootloader(const char* filename) { bool result = false; const char* msg = NULL; bool show_fatal = false; const void* data; size_t data_len; static uint8_t boot_sector[SECTOR_SIZE]; /* load the firmware */ if (!load_firmware(filename, FIRMWARE_ROCKBOX, &data, &data_len)) goto bail; /* the bootloader can only be so big */ if (data_len > BOOTLOADER_MAX_SIZE) { msg = "Aborting: bootloader too large"; goto bail; } /* only support known bootloaders */ if (!detect_valid_bootloader(data, data_len)) { msg = "Aborting: bootloader is invalid"; goto bail; } /* ask before doing anything dangerous */ if (!confirm_choice("Update bootloader?")) goto bail; /* copy the original boot sector */ rb->memcpy(boot_sector, flash(BOOT_SECTOR_OFFSET), SECTOR_SIZE); /* update the boot vector */ rb->memcpy(boot_sector, data, BOOT_VECTOR_SIZE); /* erase the boot sector */ flash_erase_sector(BOOT_SECTOR_OFFSET); /* erase the bootloader sectors */ flash_erase_sectors(BOOTLOADER_ENTRYPOINT, BOOTLOADER_SECTORS, false); /* program the new boot sector */ flash_program_bytes(BOOT_SECTOR_OFFSET, boot_sector, SECTOR_SIZE, false); /* program the new bootloader */ flash_program_bytes(BOOTLOADER_ENTRYPOINT, data, data_len, false); /* verify the new boot sector */ if (!flash_verify_bytes(BOOT_SECTOR_OFFSET, boot_sector, SECTOR_SIZE, false)) { msg = "Boot sector corrupt!"; show_fatal = true; goto bail; } /* verify the new bootloader */ if (!flash_verify_bytes(BOOTLOADER_ENTRYPOINT, data, data_len, false)) { msg = "Verify failed!"; show_fatal = true; goto bail; } /* report success */ rb->splash(HZ * 3, "Success!"); /* mark success */ result = true; bail: /* common exit code */ if (msg != NULL) rb->splash(HZ * 3, msg); if (show_fatal) show_fatal_error(); return result; } /* flash a rockbox ram / rom image */ static bool flash_rockbox(const char* filename, uint32_t offset) { bool result = false; const char* msg = NULL; const void* data; size_t data_len; struct flash_header header; /* load the firmware */ if (!load_firmware(filename, FIRMWARE_ROCKBOX, &data, &data_len)) goto bail; /* sanity check that the offset was set correctly */ if (offset != FLASH_RAMIMAGE_ENTRY && offset != FLASH_ROMIMAGE_ENTRY) { msg = "Aborting: invalid image offset"; goto bail; } /* ensure there's enough room for the ram / rom image */ if ( (offset == FLASH_RAMIMAGE_ENTRY && data_len > RAM_IMAGE_MAX_SIZE) || (offset == FLASH_ROMIMAGE_ENTRY && data_len > ROM_IMAGE_MAX_SIZE) ) { msg = "Aborting: ram / rom image too large"; goto bail; } /* check for bootloader that can load rockbox from ram / rom */ if (!detect_valid_bootloader(flash(BOOTLOADER_ENTRYPOINT), 0)) { msg = "Aborting: incompatible bootloader"; goto bail; } /* rom image specific checks */ if (offset == FLASH_ROMIMAGE_ENTRY) { uint32_t relocation = *((const uint32_t*) data); /* sanity check of the image relocation */ if (relocation != ROM_IMAGE_RELOCATION) { msg = "Aborting: invalid image relocation"; goto bail; } } /* ask before doing anything dangerous */ if (!rb->detect_original_firmware()) { if (!confirm_choice("Update Rockbox flash image?")) goto bail; } else { if (!confirm_choice("Erase original firmware?")) goto bail; } /* erase all ram / rom image sectors */ if (offset == FLASH_RAMIMAGE_ENTRY) flash_erase_sectors(offset, RAM_IMAGE_SECTORS, true); else if (offset == FLASH_ROMIMAGE_ENTRY) flash_erase_sectors(offset, ROM_IMAGE_SECTORS, true); /* prepare the header */ header.magic = FLASH_MAGIC; header.length = data_len; rb->memset(&header.version, 0x00, sizeof(header.version)); /* program the header */ flash_program_bytes(offset, &header, sizeof(header), false); /* program the ram / rom image */ flash_program_bytes(offset + sizeof(header), data, data_len, true); /* verify the header and ram / rom image */ if ( !flash_verify_bytes(offset, &header, sizeof(header), false) || !flash_verify_bytes(offset + sizeof(header), data, data_len, true) ) { msg = "Verify failed!"; /* * erase the ram / rom image header to prevent the bootloader * from trying to boot from it */ flash_erase_sector(offset); goto bail; } /* report success */ rb->splash(HZ * 3, "Success!"); /* mark success */ result = true; bail: /* common exit code */ if (msg != NULL) rb->splash(HZ * 3, msg); return result; } /* flash whole firmware; common code for romdump / original */ static bool flash_whole_firmware(const void* data, size_t data_len) { bool result = false; const char* msg = NULL; bool show_fatal = false; uint8_t boot_vector[BOOT_VECTOR_SIZE]; /* copy the original boot vector */ rb->memcpy(boot_vector, flash(BOOT_SECTOR_OFFSET), BOOT_VECTOR_SIZE); /* erase everything except the bootloader */ flash_erase_sectors(BOOT_SECTOR_OFFSET, WHOLE_FIRMWARE_SECTORS, true); /* program the original boot vector */ flash_program_bytes(BOOT_SECTOR_OFFSET, boot_vector, BOOT_VECTOR_SIZE, false); /* program the whole firmware */ flash_program_bytes(BOOT_SECTOR_OFFSET + BOOT_VECTOR_SIZE, data, data_len, true); /* verify the new boot vector */ if (!flash_verify_bytes(BOOT_SECTOR_OFFSET, boot_vector, BOOT_VECTOR_SIZE, false)) { msg = "Boot vector corrupt!"; show_fatal = true; goto bail; } /* verify the new firmware */ if (!flash_verify_bytes(BOOT_SECTOR_OFFSET + BOOT_VECTOR_SIZE, data, data_len, true)) { msg = "Verify failed!"; goto bail; } /* report success */ rb->splash(HZ * 3, "Success!"); /* mark success */ result = true; bail: /* common exit code */ if (msg != NULL) rb->splash(HZ * 3, msg); if (show_fatal) show_fatal_error(); return result; } /* flash rom dumps */ static bool flash_romdump(const char* filename) { const void* data; size_t data_len; /* load the firmware */ if (!load_firmware(filename, FIRMWARE_ROMDUMP, &data, &data_len)) return false; /* ask before doing anything dangerous */ if (!confirm_choice("Restore firmware section (bootloader will be kept)?")) return false; return flash_whole_firmware(data, data_len); } /* flash original firmware */ static bool flash_original(const char* filename) { const void* data; size_t data_len; /* load the firmware */ if (!load_firmware(filename, FIRMWARE_ORIGINAL, &data, &data_len)) return false; /* ask before doing anything dangerous */ if (!confirm_choice("Restore original firmware (bootloader will be kept)?")) return false; return flash_whole_firmware(data, data_len); } /* main function of plugin */ static void iriver_flash(const char* filename) { /* refuse to run from ROM */ const uint8_t* RB = (uint8_t*) rb; const uint8_t* FB = (uint8_t*) flash(0); if (RB >= FB && RB < FB + FLASH_SIZE) { rb->splash(HZ * 3, "Refusing to run from ROM"); return; } /* refuse to run with low battery */ if (!rb->battery_level_safe()) { rb->splash(HZ * 3, "Refusing to run with low battery"); return; } /* print information about flash; exit if not supported */ if (!show_info()) return; /* exit if no filename was provided */ if (filename == NULL) { rb->splash(HZ * 3, "Please use this plugin with \"Open with...\""); return; } /* choose what to do with the file */ if (rb->strcasestr(filename, "/bootloader.iriver") != NULL) flash_bootloader(filename); else if (rb->strcasestr(filename, "/rockbox.iriver") != NULL) flash_rockbox(filename, FLASH_RAMIMAGE_ENTRY); else if (rb->strcasestr(filename, "/rombox.iriver") != NULL) flash_rockbox(filename, FLASH_ROMIMAGE_ENTRY); else if (rb->strcasestr(filename, ROMDUMP) != NULL) flash_romdump(filename); else if (rb->strcasestr(filename, ORIGINAL) != NULL) flash_original(filename); else rb->splash(HZ * 3, "Unknown file type"); } /* plugin entry point */ enum plugin_status plugin_start(const void* parameter) { /* need to disable memguard to write to flash */ int mode = rb->system_memory_guard(MEMGUARD_NONE); /* setup LCD font */ rb->lcd_setfont(FONT_SYSFIXED); /* don't let the backlight turn off or it might scare people */ backlight_ignore_timeout(); /* run the main entry function */ iriver_flash(parameter); /* restore the original backlight settings */ backlight_use_settings(); /* restore LCD font */ rb->lcd_setfont(FONT_UI); /* restore original memory guard setting */ rb->system_memory_guard(mode); return PLUGIN_OK; }