diff --git a/bootloader/SOURCES b/bootloader/SOURCES index 57c23b115c..f72c58a0b7 100644 --- a/bootloader/SOURCES +++ b/bootloader/SOURCES @@ -90,5 +90,5 @@ show_logo.c sansaconnect.c show_logo.c #elif defined(FIIO_M3K) -fiiom3k.c +x1000.c #endif diff --git a/bootloader/fiiom3k.c b/bootloader/fiiom3k.c deleted file mode 100644 index 42b3260e07..0000000000 --- a/bootloader/fiiom3k.c +++ /dev/null @@ -1,360 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2021 Aidan MacDonald - * - * 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 "system.h" -#include "core_alloc.h" -#include "kernel/kernel-internal.h" -#include "i2c.h" -#include "power.h" -#include "lcd.h" -#include "font.h" -#include "backlight.h" -#include "backlight-target.h" -#include "button.h" -#include "storage.h" -#include "file_internal.h" -#include "disk.h" -#include "usb.h" -#include "rb-loader.h" -#include "loader_strerror.h" -#include "version.h" -#include "installer-fiiom3k.h" -#include "boot-x1000.h" -#include "x1000/cpm.h" - -/* Load address where the binary needs to be placed */ -extern unsigned char loadaddress[]; - -/* Fixed buffer to contain the loaded binary in memory */ -extern unsigned char loadbuffer[]; -extern unsigned char loadbufferend[]; -#define MAX_LOAD_SIZE (loadbufferend - loadbuffer) - -void exec(void* dst, const void* src, int bytes) - __attribute__((noreturn, section(".icode"))); - -void exec(void* dst, const void* src, int bytes) -{ - memcpy(dst, src, bytes); - commit_discard_idcache(); - __asm__ __volatile__ ("jr %0\n" - "nop\n" - :: "r"(dst)); - __builtin_unreachable(); -} - -static bool lcd_inited = false; -static bool usb_inited = false; -static bool disk_inited = false; - -static void init_lcd(void) -{ - if(lcd_inited) - return; - - lcd_init(); - font_init(); - lcd_setfont(FONT_SYSFIXED); - - /* Clear screen before turning backlight on, otherwise we might - * display random garbage on the screen */ - lcd_clear_display(); - lcd_update(); - - backlight_init(); - - lcd_inited = true; -} - -static void put_version(void) -{ - lcd_putsxy((LCD_WIDTH - (SYSFONT_WIDTH * strlen(rbversion))) / 2, - (LCD_HEIGHT - SYSFONT_HEIGHT), rbversion); -} - -static void do_splash2(int delay, const char* msg, const char* msg2) -{ - init_lcd(); - lcd_clear_display(); - lcd_putsxy((LCD_WIDTH - (SYSFONT_WIDTH * strlen(msg))) / 2, - (LCD_HEIGHT - SYSFONT_HEIGHT) / 2, msg); - if(msg2) { - lcd_putsxy((LCD_WIDTH - (SYSFONT_WIDTH * strlen(msg2))) / 2, - (LCD_HEIGHT + 2*SYSFONT_HEIGHT) / 2, msg2); - } - - put_version(); - lcd_update(); - sleep(delay); -} - -static void do_splash(int delay, const char* msg) -{ - do_splash2(delay, msg, NULL); -} - -static void do_usb(void) -{ - if(!usb_inited) { - usb_init(); - usb_start_monitoring(); - usb_inited = true; - } - - do_splash2(0, "Waiting for USB", "Press POWER to go back"); - - int btn; - while(1) { - btn = button_get(true); - if(btn == SYS_USB_CONNECTED) - break; - else if(btn == BUTTON_POWER) - return; - } - - do_splash(0, "USB mode"); - usb_acknowledge(SYS_USB_CONNECTED_ACK); - while(button_get(true) != SYS_USB_DISCONNECTED); - - do_splash(3*HZ, "USB disconnected"); -} - -static int init_disk(void) -{ - if(disk_inited) - return 0; - - while(!storage_present(0)) { - do_splash2(0, "Insert SD card", "Press POWER for recovery"); - int btn = button_get_w_tmo(HZ); - if(btn == BUTTON_POWER) - return 1; - } - - if(disk_mount_all() <= 0) { - do_splash(5*HZ, "Cannot mount filesystem"); - return 1; - } - - disk_inited = true; - return 0; -} - -static void do_boot(void) -{ - if(init_disk() != 0) - return; - - int loadsize = load_firmware(loadbuffer, BOOTFILE, MAX_LOAD_SIZE); - if(loadsize <= 0) { - do_splash2(5*HZ, "Error loading Rockbox", - loader_strerror(loadsize)); - do_usb(); - return; - } - - if(lcd_inited) - backlight_hw_off(); - - disable_irq(); - exec(loadaddress, loadbuffer, loadsize); -} - -#define INSTALL 0 -#define BACKUP 1 -#define RESTORE 2 - -static void do_install(int which) -{ - int rc = init_disk(); - if(rc != 0) { - do_splash2(5*HZ, "Install aborted", "No SD card present"); - return; - } - - const char* msg; - if(rc == INSTALL) - msg = "Installing"; - else if(rc == BACKUP) - msg = "Backing up"; - else - msg = "Restoring backup"; - - do_splash(0, msg); - - if(which == INSTALL) - rc = install_boot("/bootloader.m3k"); - else if(which == BACKUP) - rc = backup_boot("/fiiom3k-boot.bin"); - else - rc = restore_boot("/fiiom3k-boot.bin"); - - char buf[32]; - snprintf(buf, sizeof(buf), "Failed! Error: %d", rc); - const char* msg1 = rc == 0 ? "Success" : buf; - const char* msg2 = "Press POWER to continue"; - do_splash2(0, msg1, msg2); - - button_clear_queue(); - while(button_get(true) != BUTTON_POWER); -} - -static void recovery_menu(void) -{ - static const char* items[] = { - "--- Rockbox recovery menu ---", - "[System]", - " Start Rockbox", - " USB mode", - " Shutdown", - " Reboot", - "[Bootloader]", - " Install or update", - " Backup", - " Restore", - "", - "", - "", - "", - "", - "VOL+/VOL- move cursor", - "PLAY select item", - "POWER power off", - }; - - static const int nitems = sizeof(items) / sizeof(char*); - - init_lcd(); - - int selection = 2; - do { - /* Draw menu */ - lcd_clear_display(); - - for(int i = 0; i < nitems; ++i) - lcd_puts(0, i, items[i]); - - if(items[selection][0] == ' ') - lcd_puts(0, selection, "=>"); - - put_version(); - lcd_update(); - - /* Clear queue to avoid accidental input */ - button_clear_queue(); - - /* Get the button */ - int btn = button_get(true); - - /* Process user input */ - if(btn == BUTTON_VOL_UP) { - for(int i = selection-1; i >= 0; --i) { - if(items[i][0] == ' ') { - selection = i; - break; - } - } - - continue; - } else if(btn == BUTTON_VOL_DOWN) { - for(int i = selection+1; i < nitems; ++i) { - if(items[i][0] == ' ') { - selection = i; - break; - } - } - - continue; - } else if(btn == BUTTON_POWER) { - selection = 4; /* Shutdown */ - } else if(btn != BUTTON_PLAY) { - continue; - } - - /* User pressed PLAY so decide what action to take */ - switch(selection) { - case 2: /* Start rockbox */ - do_boot(); - break; - - case 3: /* USB mode */ - do_usb(); - break; - - case 4: /* Shutdown */ - do_splash(HZ, "Shutting down"); - power_off(); - break; - - case 5: /* Reboot */ - do_splash(HZ, "Rebooting"); - system_reboot(); - break; - - case 7: /* Install bootloader */ - do_install(INSTALL); - break; - - case 8: /* Backup bootloader */ - do_install(BACKUP); - break; - - case 9: /* Restore bootloader */ - do_install(RESTORE); - break; - - default: - break; - } - } while(1); -} - -void main(void) -{ - bool recovery_mode = false; - - if(get_boot_flag(BOOT_FLAG_USB_BOOT)) - recovery_mode = true; - - system_init(); - core_allocator_init(); - kernel_init(); - i2c_init(); - power_init(); - button_init(); - enable_irq(); - - if(storage_init() < 0) { - do_splash(3*HZ, "Failed to init storage"); - power_off(); - } - - filesystem_init(); - - if(button_read_device() & BUTTON_VOL_UP) - recovery_mode = true; - - if(!recovery_mode) - do_boot(); - - /* If boot fails or user holds Vol+, go to recovery menu */ - recovery_menu(); -} diff --git a/bootloader/x1000.c b/bootloader/x1000.c new file mode 100644 index 0000000000..0b28b7449d --- /dev/null +++ b/bootloader/x1000.c @@ -0,0 +1,490 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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. + * + ****************************************************************************/ + +/* Unified bootloader for all X1000 targets. This is a bit messy. + * + * Features: + * - Text based user interface + * - USB mass storage access + * - Bootloader installation / backup / restore + * + * Possible future improvements: + * - Allow booting original firmware from the UI + */ + +#include "system.h" +#include "core_alloc.h" +#include "kernel/kernel-internal.h" +#include "i2c.h" +#include "power.h" +#include "lcd.h" +#include "font.h" +#include "backlight.h" +#include "backlight-target.h" +#include "button.h" +#include "storage.h" +#include "file_internal.h" +#include "disk.h" +#include "usb.h" +#include "rb-loader.h" +#include "loader_strerror.h" +#include "version.h" +#include "boot-x1000.h" +#include +#include +#include +#include +#include + +#ifdef FIIO_M3K +# include "installer-fiiom3k.h" +#endif + +#if defined(FIIO_M3K) +# define BL_RECOVERY BUTTON_VOL_UP +# define BL_UP BUTTON_VOL_UP +# define BL_DOWN BUTTON_VOL_DOWN +# define BL_SELECT BUTTON_PLAY +# define BL_QUIT BUTTON_POWER +# define BL_UP_NAME "VOL+" +# define BL_DOWN_NAME "VOL-" +# define BL_SELECT_NAME "PLAY" +# define BL_QUIT_NAME "POWER" +#else +# error "Missing keymap!" +#endif + +enum { + MENUITEM_HEADING, + MENUITEM_ACTION, +}; + +struct menuitem { + int type; + const char* text; + void(*action)(void); +}; + +void clearscreen(void); +void putversion(void); +void putcenter_y(int y, const char* msg); +void putcenter_line(int line, const char* msg); +void splash2(long delay, const char* msg, const char* msg2); +void splash(long delay, const char* msg); + +void init_lcd(void); +void init_usb(void); +int init_disk(void); + +void recovery_menu(void) __attribute__((noreturn)); + +void boot_rockbox(void); +void usb_mode(void); +void shutdown(void); +void reboot(void); +void bootloader_install(void); +void bootloader_backup(void); +void bootloader_restore(void); + +/* Defines the recovery menu contents */ +const struct menuitem recovery_items[] = { + {MENUITEM_HEADING, "System", NULL}, + {MENUITEM_ACTION, "Start Rockbox", &boot_rockbox}, + {MENUITEM_ACTION, "USB mode", &usb_mode}, + {MENUITEM_ACTION, "Shutdown", &shutdown}, + {MENUITEM_ACTION, "Reboot", &reboot}, + {MENUITEM_HEADING, "Bootloader", NULL}, + {MENUITEM_ACTION, "Install or update", &bootloader_install}, + {MENUITEM_ACTION, "Backup", &bootloader_backup}, + {MENUITEM_ACTION, "Restore", &bootloader_restore}, +}; + +/* Final load address of rockbox binary. + * NOTE: this is really the load address of the bootloader... it relies + * on the fact that bootloader and app are linked at the same address. */ +extern unsigned char loadaddress[]; + +/* Temp buffer to contain the binary in memory */ +extern unsigned char loadbuffer[]; +extern unsigned char loadbufferend[]; +#define MAX_LOAD_SIZE (loadbufferend - loadbuffer) + +/* Flags to indicate if hardware was already initialized */ +bool lcd_inited = false; +bool usb_inited = false; +bool disk_inited = false; + +/* Jump to loaded binary */ +void exec(void* dst, const void* src, size_t bytes) + __attribute__((noinline, noreturn, section(".icode"))); + +void exec(void* dst, const void* src, size_t bytes) +{ + memcpy(dst, src, bytes); + commit_discard_idcache(); + + typedef void(*entry_fn)(void) __attribute__((noreturn)); + entry_fn fn = (entry_fn)dst; + fn(); +} + +void clearscreen(void) +{ + init_lcd(); + lcd_clear_display(); + putversion(); +} + +void putversion(void) +{ + int x = (LCD_WIDTH - SYSFONT_WIDTH*strlen(rbversion)) / 2; + int y = LCD_HEIGHT - SYSFONT_HEIGHT; + lcd_putsxy(x, y, rbversion); +} + +void putcenter_y(int y, const char* msg) +{ + int x = (LCD_WIDTH - SYSFONT_WIDTH*strlen(msg)) / 2; + lcd_putsxy(x, y, msg); +} + +void putcenter_line(int line, const char* msg) +{ + int y = LCD_HEIGHT/2 + (line - 1)*SYSFONT_HEIGHT; + putcenter_y(y, msg); +} + +void splash2(long delay, const char* msg, const char* msg2) +{ + clearscreen(); + putcenter_line(0, msg); + if(msg2) + putcenter_line(1, msg2); + lcd_update(); + sleep(delay); +} + +void splash(long delay, const char* msg) +{ + splash2(delay, msg, NULL); +} + +void init_lcd(void) +{ + if(lcd_inited) + return; + + lcd_init(); + font_init(); + lcd_setfont(FONT_SYSFIXED); + + /* Clear screen before turning backlight on, otherwise we might + * display random garbage on the screen */ + lcd_clear_display(); + lcd_update(); + + backlight_init(); + + lcd_inited = true; +} + +/* TODO: This does not work properly after a USB boot. + * + * The USB core is not properly reset by just re-initializing it, and I can't + * figure out how to make it work. Setting the DWC_DCTL.SDIS bit will force a + * disconnect (the usb-designware driver does this already as part of its init + * but it doesn't seem to cause a disconnect). + * + * But the host still doesn't detect us properly when we reconnect. Linux gives + * messages "usb 1-3: config 1 has no interfaces?" in dmesg and no mass storage + * interfaces show up. + * + * Re-plugging the cable seems to reset everything to a working state and there + * are no issues, but that's annoying. + */ +void init_usb(void) +{ + if(usb_inited) + return; + + usb_init(); + usb_start_monitoring(); + usb_inited = true; +} + +int init_disk(void) +{ + if(disk_inited) + return 0; + + button_clear_queue(); + while(!storage_present(IF_MD(0))) { + splash2(0, "Insert SD card", "Press " BL_QUIT_NAME " for recovery"); + if(button_get_w_tmo(HZ/4) == BL_QUIT) + return -1; + } + + if(disk_mount_all() <= 0) { + splash(5*HZ, "Cannot mount disk"); + return -1; + } + + disk_inited = true; + return 0; +} + +void put_help_line(int line, const char* str1, const char* str2) +{ + int width = LCD_WIDTH / SYSFONT_WIDTH; + lcd_puts(0, line, str1); + lcd_puts(width - strlen(str2), line, str2); +} + +void recovery_menu(void) +{ + const int n_items = sizeof(recovery_items)/sizeof(struct menuitem); + + int selection = 0; + while(recovery_items[selection].type != MENUITEM_ACTION) + ++selection; + + while(1) { + clearscreen(); + putcenter_y(0, "Rockbox recovery menu"); + + int top_line = 2; + + /* draw the menu */ + for(int i = 0; i < n_items; ++i) { + switch(recovery_items[i].type) { + case MENUITEM_HEADING: + lcd_putsf(0, top_line+i, "[%s]", recovery_items[i].text); + break; + + case MENUITEM_ACTION: + lcd_puts(3, top_line+i, recovery_items[i].text); + break; + + default: + break; + } + } + + /* draw the selection marker */ + lcd_puts(0, top_line+selection, "=>"); + + /* draw the help text */ + int line = (LCD_HEIGHT - SYSFONT_HEIGHT)/SYSFONT_HEIGHT - 3; + put_help_line(line++, BL_DOWN_NAME "/" BL_UP_NAME, "move cursor"); + put_help_line(line++, BL_SELECT_NAME, "select item"); + put_help_line(line++, BL_QUIT_NAME, "power off"); + + lcd_update(); + + /* handle input */ + button_clear_queue(); + switch(button_get(true)) { + case BL_SELECT: { + if(recovery_items[selection].action) + recovery_items[selection].action(); + } break; + + case BL_UP: + for(int i = selection-1; i >= 0; --i) { + if(recovery_items[i].action) { + selection = i; + break; + } + } + break; + + case BL_DOWN: + for(int i = selection+1; i < n_items; ++i) { + if(recovery_items[i].action) { + selection = i; + break; + } + } + break; + + case BL_QUIT: + shutdown(); + break; + + default: + break; + } + } +} + +void boot_rockbox(void) +{ + if(init_disk() != 0) + return; + + int rc = load_firmware(loadbuffer, BOOTFILE, MAX_LOAD_SIZE); + if(rc <= 0) { + splash2(5*HZ, "Error loading Rockbox", loader_strerror(rc)); + return; + } + + if(lcd_inited) + backlight_hw_off(); + + disable_irq(); + exec(loadaddress, loadbuffer, rc); +} + +void usb_mode(void) +{ + init_usb(); + splash2(0, "Waiting for USB", "Press " BL_QUIT_NAME " to go back"); + + while(1) { + int btn = button_get(true); + if(btn == SYS_USB_CONNECTED) + break; + else if(btn == BL_QUIT) + return; + } + + splash(0, "USB mode"); + usb_acknowledge(SYS_USB_CONNECTED_ACK); + while(button_get(true) != SYS_USB_DISCONNECTED); + + splash(3*HZ, "USB disconnected"); +} + +void shutdown(void) +{ + splash(HZ, "Shutting down"); + power_off(); + while(1); +} + +void reboot(void) +{ + splash(HZ, "Rebooting"); + system_reboot(); + while(1); +} + +/* TODO: clean this up, make the installer generic as well */ +enum { + INSTALL, + BACKUP, + RESTORE, +}; + +#ifdef FIIO_M3K +void bootloader_action(int which) +{ + if(init_disk() != 0) { + splash2(5*HZ, "Install aborted", "Cannot access SD card"); + return; + } + + const char* msg; + switch(which) { + case INSTALL: msg = "Installing"; break; + case BACKUP: msg = "Backing up"; break; + case RESTORE: msg = "Restoring"; break; + default: return; /* can't happen */ + } + + splash(0, msg); + + int rc; + switch(which) { + case INSTALL: rc = install_boot("/bootloader.m3k"); break; + case BACKUP: rc = backup_boot("/fiiom3k-boot.bin"); break; + case RESTORE: rc = restore_boot("/fiiom3k-boot.bin"); break; + default: return; + } + + static char buf[64]; + snprintf(buf, sizeof(buf), "Failed! Error: %d", rc); + const char* msg1 = rc == 0 ? "Success" : buf; + const char* msg2 = "Press " BL_QUIT_NAME " to continue"; + splash2(0, msg1, msg2); + + button_clear_queue(); + while(button_get(true) != BL_QUIT); +} +#else +void bootloader_action(int which) +{ + (void)which; + splash(5*HZ, "Not implemented!"); +} +#endif + +void bootloader_install(void) +{ + bootloader_action(INSTALL); +} + +void bootloader_backup(void) +{ + bootloader_action(BACKUP); +} + +void bootloader_restore(void) +{ + bootloader_action(RESTORE); +} + +void main(void) +{ + system_init(); + core_allocator_init(); + kernel_init(); + i2c_init(); + power_init(); + button_init(); + enable_irq(); + + if(storage_init() < 0) { + splash(5*HZ, "storage_init() failed"); + power_off(); + } + + filesystem_init(); + + /* If USB booting, the user probably needs to enter recovery mode; + * let's not force them to hold down the recovery key. */ + bool recovery_mode = get_boot_flag(BOOT_FLAG_USB_BOOT); + +#ifdef HAVE_BUTTON_DATA + int bdata; + if(button_read_device(&bdata) & BL_RECOVERY) +#else + if(button_read_device() & BL_RECOVERY) +#endif + recovery_mode = true; + + /* If boot fails, it will return and continue on below */ + if(!recovery_mode) + boot_rockbox(); + + /* This function does not return. */ + recovery_menu(); +}