1
0
Fork 0
forked from len0rd/rockbox
foxbox/bootloader/x1000.c
Aidan MacDonald 83c2398384 x1000: Fix USB connection problems in bootloader
This problem actually had nothing to do with USB boot; it's
because the cable is plugged in when the USB mode menu item
is selected. The USB thread detected the select button press
and went into charge-only mode (as it usually does when you
hold down a key in Rockbox). This is fixed by having the USB
thread ignore most keys in the bootloader.

USB connect events are delivered via the button queue, and
there were also cases where the connection could be missed
if the event happened within another UI screen. This should
also be fixed.

Change-Id: I077d705a6ac845c8713219eee45d26aa6addfa61
2022-01-02 20:11:03 +00:00

499 lines
12 KiB
C

/***************************************************************************
* __________ __ ___.
* 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 "installer-x1000.h"
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#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"
# define BOOTBACKUP_FILE "/fiiom3k-boot.bin"
#elif defined(SHANLING_Q1)
# define BL_RECOVERY BUTTON_NEXT
# define BL_UP BUTTON_PREV
# define BL_DOWN BUTTON_NEXT
# define BL_SELECT BUTTON_PLAY
# define BL_QUIT BUTTON_POWER
# define BL_UP_NAME "PREV"
# define BL_DOWN_NAME "NEXT"
# define BL_SELECT_NAME "PLAY"
# define BL_QUIT_NAME "POWER"
# define BOOTBACKUP_FILE "/shanlingq1-boot.bin"
#elif defined(EROS_QN)
# define BL_RECOVERY BUTTON_VOL_UP
# define BL_UP BUTTON_SCROLL_BACK
# define BL_DOWN BUTTON_SCROLL_FWD
# define BL_SELECT BUTTON_PLAY
# define BL_QUIT BUTTON_POWER
# define BL_UP_NAME "Up"
# define BL_DOWN_NAME "Scroll Down"
# define BL_SELECT_NAME "PLAY"
# define BL_QUIT_NAME "POWER"
# define BOOTBACKUP_FILE "/erosqnative-boot.bin"
#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;
/* Set to true if a SYS_USB_CONNECTED event is seen
* Set to false if a SYS_USB_DISCONNECTED event is seen */
bool is_usb_connected = 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);
}
int get_button(int timeout)
{
int btn = button_get_w_tmo(timeout);
if(btn == SYS_USB_CONNECTED)
is_usb_connected = true;
else if(btn == SYS_USB_DISCONNECTED)
is_usb_connected = false;
return btn;
}
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;
}
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;
while(!storage_present(IF_MD(0))) {
splash2(0, "Insert SD card", "Press " BL_QUIT_NAME " for recovery");
if(get_button(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 */
switch(get_button(TIMEOUT_BLOCK)) {
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();
if(!is_usb_connected)
splash2(0, "Waiting for USB", "Press " BL_QUIT_NAME " to go back");
while(!is_usb_connected)
if(get_button(TIMEOUT_BLOCK) == BL_QUIT)
return;
splash(0, "USB mode");
usb_acknowledge(SYS_USB_CONNECTED_ACK);
while(is_usb_connected)
get_button(TIMEOUT_BLOCK);
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);
}
enum {
INSTALL,
BACKUP,
RESTORE,
};
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_bootloader("/bootloader." BOOTFILE_EXT); break;
case BACKUP: rc = backup_bootloader(BOOTBACKUP_FILE); break;
case RESTORE: rc = restore_bootloader(BOOTBACKUP_FILE); break;
default: return;
}
static char buf[64];
snprintf(buf, sizeof(buf), "%s (%d)", installer_strerror(rc), rc);
const char* msg1 = rc == 0 ? "Success" : buf;
const char* msg2 = "Press " BL_QUIT_NAME " to continue";
splash2(0, msg1, msg2);
while(get_button(TIMEOUT_BLOCK) != BL_QUIT);
}
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();
}