rockbox/bootloader/echoplayer.c
Aidan MacDonald 19af7131e2 echoplayer: allow enabling system debug in normal builds
Allow toggling the system debug state from the debug menu
in Rockbox, or by holding a button combo at boot, so that
an SWD/JTAG debugger can be attached to normal non-debug
builds without too much hassle.

Change-Id: Iee47ef916ade2e5ec1094a63c68e48f1b27b0bbb
2026-02-06 07:09:32 -05:00

493 lines
13 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2025 by 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 "kernel/kernel-internal.h"
#include "system.h"
#include "power.h"
#include "lcd.h"
#include "backlight.h"
#include "button.h"
#include "storage.h"
#include "disk.h"
#include "file.h"
#include "file_internal.h"
#include "usb.h"
#include "elf.h"
#include "elf_loader.h"
#include "rbversion.h"
#include "system-echoplayer.h"
#include "gpio-stm32h7.h"
#include "regs/cortex-m/cm_debug.h"
#define SDRAM_SIZE (MEMORYSIZE * 1024 * 1024)
/* Address where Rockbox .elf binary will be cached in RAM */
#define LOAD_SIZE (2 * 1024 * 1024)
#define LOAD_BUFFER_ADDR (STM32_SDRAM1_BASE + SDRAM_SIZE - LOAD_SIZE)
/* Values at GDB_MAGICx */
#define GDB_MAGICVAL1 0x726f636b
#define GDB_MAGICVAL2 0x424f4f54
#define GDB_MAGICVAL3 0x6764626c
#define GDB_MAGICVAL4 0x6f616455
/* Addresses used by GDB boot protocol */
#define GDB_MAGIC1 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x00))
#define GDB_MAGIC2 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x04))
#define GDB_MAGIC3 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x08))
#define GDB_MAGIC4 (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x0c))
#define GDB_ELFADDR (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x10))
#define GDB_ELFSIZE (*(volatile uint32_t*)(STM32_SRAM4_BASE + 0x14))
/* Events for the monitor callback to signal the main thread */
#define EV_POWER_PRESSED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0)
#define EV_POWER_RELEASED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1)
#define EV_USB_UNPLUGGED MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 2)
/* Long press duration for power button */
#define POWERBUTTON_LONG_PRESS_TIME (HZ)
/* Time to remain powered with no USB cable inserted */
#define USB_UNPLUGGED_ACTIVE_TIME (30 * HZ)
#define USB_UNPLUGGED_INACTIVE_TIME (3 * HZ)
static const struct elf_memory_map rb_elf_mmap[] = {
{
.addr = STM32_ITCM_BASE,
.size = STM32_ITCM_SIZE,
.flags = PF_R | PF_X,
},
{
.addr = STM32_DTCM_BASE,
.size = STM32_DTCM_SIZE,
.flags = PF_R | PF_W,
},
{
.addr = STM32_SDRAM1_BASE,
.size = SDRAM_SIZE - LOAD_SIZE,
.flags = PF_R | PF_W | PF_X,
},
};
static const struct elf_load_context rb_elf_ctx = {
.mmap = rb_elf_mmap,
.num_mmap = ARRAYLEN(rb_elf_mmap),
};
/* Power button monitor state */
static bool pwr_curr_state;
static bool pwr_prev_state;
struct timeout pwr_stable_tmo;
/* USB monitor state */
static bool usb_curr_state;
static bool usb_prev_state;
struct timeout usb_unplugged_tmo;
static volatile bool restart_pwr_stable_tmo;
static volatile bool restart_usb_unplugged_tmo;
/*
* Because power is always enabled while USB is plugged in the
* bootloader decides whether to appear "active" or "inactive"
* to the user.
*/
static bool is_active;
/*
* This flag is set if the bootloader is entered after software
* poweroff. The user may still be holding the power button and
* we don't want to boot Rockbox because of this, so we have to
* wait for the power button to be released first.
*/
static bool wait_for_power_released;
/* Optional error message displayed on LCD */
static const char *status_msg = NULL;
/* Location of Rockbox ELF binary in memory */
static void *elf_load_addr = NULL;
static size_t elf_load_size = 0;
/* Helper functions */
static bool is_power_button_pressed(void)
{
return button_status() & BUTTON_POWER;
}
static bool is_usbmode_button_pressed(void)
{
return button_status() & BUTTON_DOWN;
}
static bool is_sysdebug_button_pressed(void)
{
const int mask = BUTTON_A | BUTTON_B;
/* Both buttons must be held */
return (button_status() & mask) == mask;
}
static int send_event_on_tmo(struct timeout *tmo)
{
button_queue_post(tmo->data, 0);
return 0;
}
/*
* Monitors the state of the power button and USB cable
* insertion status. It will post an event to the main
* thread when (a) the power button is continously held
* or released for long enough, or (b) the USB cable is
* unplugged for long enough.
*/
static void monitor_tick(void)
{
/* Power button state */
pwr_prev_state = pwr_curr_state;
pwr_curr_state = is_power_button_pressed();
if (pwr_curr_state != pwr_prev_state || restart_pwr_stable_tmo)
{
long event = pwr_curr_state ? EV_POWER_PRESSED : EV_POWER_RELEASED;
int ticks = POWERBUTTON_LONG_PRESS_TIME;
restart_pwr_stable_tmo = false;
timeout_register(&pwr_stable_tmo, send_event_on_tmo, ticks, event);
}
/* USB cable state */
usb_prev_state = usb_curr_state;
usb_curr_state = usb_inserted();
/* Ignore cable state change in inactive state */
if (usb_curr_state != usb_prev_state || restart_usb_unplugged_tmo)
{
long event = EV_USB_UNPLUGGED;
int ticks = is_active ? USB_UNPLUGGED_ACTIVE_TIME : USB_UNPLUGGED_INACTIVE_TIME;
restart_usb_unplugged_tmo = false;
if (usb_curr_state)
timeout_cancel(&usb_unplugged_tmo);
else
timeout_register(&usb_unplugged_tmo, send_event_on_tmo, ticks, event);
}
}
static void monitor_init(void)
{
pwr_curr_state = is_power_button_pressed();
usb_curr_state = usb_inserted();
/* Make sure events fire even if inputs don't change after boot */
restart_pwr_stable_tmo = true;
restart_usb_unplugged_tmo = true;
tick_add_task(monitor_tick);
}
static void go_active(void)
{
is_active = true;
restart_usb_unplugged_tmo = true;
gpio_set_level(GPIO_CPU_POWER_ON, 1);
storage_enable(true);
disk_mount_all();
}
static void go_inactive(void)
{
is_active = false;
restart_usb_unplugged_tmo = true;
storage_enable(false);
gpio_set_level(GPIO_CPU_POWER_ON, 0);
}
static void refresh_display(void)
{
if (!is_active)
{
lcd_shutdown();
return;
}
int y = 0;
lcd_clear_display();
lcd_putsf(0, y++, "Rockbox on %s", MODEL_NAME);
y++;
if (status_msg)
{
lcd_putsf(0, y++, "Error: %s", status_msg);
y++;
}
if (!usb_inserted())
{
lcd_putsf(0, y++, "Hold POWER to power off");
lcd_putsf(0, y++, "Connect USB cable for USB mode");
y++;
}
else
{
lcd_putsf(0, y++, "Bootloader USB mode");
if (charging_state())
{
lcd_putsf(0, y++, "Battery charging (%d mA)",
usb_charging_maxcurrent());
}
else
{
lcd_putsf(0, y++, "Battery charged");
}
y++;
}
lcd_putsf(0, y++, "Version: %s", RBVERSION);
lcd_update();
lcd_enable(true);
}
static bool load_rockbox(void)
{
int fd = open(BOOTDIR "/" BOOTFILE, O_RDONLY);
if (fd < 0)
{
status_msg = "Rockbox not found";
return false;
}
void *tmp_buf = (void *)LOAD_BUFFER_ADDR;
size_t tmp_size = LOAD_SIZE;
ssize_t ret = read(fd, tmp_buf, tmp_size);
if (ret < 0)
{
status_msg = "I/O error loading Rockbox";
goto out;
}
if ((size_t)ret == tmp_size)
{
status_msg = "Rockbox binary too large";
goto out;
}
elf_load_addr = tmp_buf;
elf_load_size = tmp_size;
out:
close(fd);
return elf_load_addr != NULL;
}
static void launch_elf(void)
{
void *entrypoint = NULL;
void (*entry_fn) (void) = NULL;
if (elf_load_addr == NULL || elf_load_size == 0)
return;
int err = elf_loadmem(elf_load_addr, elf_load_size, &rb_elf_ctx, &entrypoint);
if (err)
{
status_msg = "Failed to execute Rockbox";
return;
}
disk_unmount_all();
storage_enable(false);
lcd_shutdown();
entry_fn = entrypoint;
commit_discard_idcache();
disable_irq();
stm32_systick_disable();
entry_fn();
}
static void launch(void)
{
/* No-op if USB mode was requested */
if (is_usbmode_button_pressed())
return;
load_rockbox();
launch_elf();
}
static bool handle_gdb_boot(void)
{
/* Look for magic values that signal the GDB boot protocol */
bool gdb_boot = (GDB_MAGIC1 == GDB_MAGICVAL1 &&
GDB_MAGIC2 == GDB_MAGICVAL2 &&
GDB_MAGIC3 == GDB_MAGICVAL3 &&
GDB_MAGIC4 == GDB_MAGICVAL4);
/* Clear them so they won't hang around on a system reset */
GDB_MAGIC1 = 0;
GDB_MAGIC2 = 0;
GDB_MAGIC3 = 0;
GDB_MAGIC4 = 0;
if (!gdb_boot)
return false;
/* "Call" GDB by entering breakpoint */
GDB_ELFADDR = 0;
GDB_ELFSIZE = 0;
asm volatile("bkpt");
/* Read location of the loaded binary */
elf_load_addr = (void *)GDB_ELFADDR;
elf_load_size = (size_t)GDB_ELFSIZE;
return true;
}
void main(void)
{
system_init();
kernel_init();
power_init();
button_init();
/* Enable system debugging if button combo held or debugger attached */
if (reg_readf(CM_DEBUG_DHCSR, C_DEBUGEN) ||
is_sysdebug_button_pressed())
{
system_debug_enable(true);
}
/* Start monitoring power button / usb state */
monitor_init();
/* Prepare LCD in case we need to display something */
lcd_init();
backlight_init();
/*
* Prepare storage subsystem, but keep SD card unpowered
* until we actually need to access it.
*/
storage_init();
storage_enable(false);
/*
* Initialize fs/disk internal state, the disk will not
* be mountable due to being disabled but this will not
* cause any fatal errors.
*/
filesystem_init();
disk_mount_all();
/* GDB assisted boot takes precedence */
if (handle_gdb_boot())
launch_elf();
if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_REBOOT ||
usb_detect() == USB_INSERTED)
{
/*
* For software reboot we want to immediately launch Rockbox.
*
* We also do so if USB is plugged in at boot, because there's
* no way to dynamically switch between charge only and mass
* storage mode depending on the active/inactive state; to avoid
* confusion, it is simpler to just boot Rockbox.
*/
go_active();
launch();
}
else if (echoplayer_boot_reason == ECHOPLAYER_BOOT_REASON_SW_POWEROFF)
{
/* Ignore power button pressed event until button is first released */
wait_for_power_released = true;
}
/*
* Initialize USB so we can enumerate with the host and
* negotiate charge current (needed even in inactive mode)
*/
usb_init();
usb_start_monitoring();
usb_charging_enable(USB_CHARGING_FORCE);
for (;;)
{
refresh_display();
long refresh_tmo = is_active ? HZ : TIMEOUT_BLOCK;
switch (button_get_w_tmo(refresh_tmo))
{
case EV_POWER_PRESSED:
if (wait_for_power_released)
break;
if (!is_active)
{
/* Launch Rockbox due to user pressing power button */
go_active();
launch();
}
else
{
/*
* NOTE: The check for USB insertion here is only
* because we can't change to charging only mode.
*/
if (!usb_inserted())
go_inactive();
}
break;
case EV_POWER_RELEASED:
/*
* This cuts power if the power button is not
* held and we're not in the active state due
* to USB mode, etc.
*/
wait_for_power_released = false;
gpio_set_level(GPIO_CPU_POWER_ON, is_active);
break;
case EV_USB_UNPLUGGED:
go_inactive();
break;
case SYS_USB_CONNECTED:
go_active();
usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data());
break;
}
}
}