mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
Rockbox only drives NAND parts proven on hardware, and this tree has one model to prove them on. This is the image that lets an owner of another Nano 3G supply what validating theirs takes: a bootloader built with -DNAND_CHECK and run from DFU, which never touches the NOR flash or writes to the NAND. Testing evidence: firmware/target/arm/s5l8702/ipodnano3g/TESTING.md. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Change-Id: I46328b69f8011337790f35a195de97b5bcf347a5
1147 lines
32 KiB
C
1147 lines
32 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2005 by Dave Chapman
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "inttypes.h"
|
|
#include "cpu.h"
|
|
#include "system.h"
|
|
#include "lcd.h"
|
|
#include "../kernel-internal.h"
|
|
#include "file_internal.h"
|
|
#include "storage.h"
|
|
#include "disk.h"
|
|
#include "font.h"
|
|
#include "backlight.h"
|
|
#include "backlight-target.h"
|
|
#include "button.h"
|
|
#include "panic.h"
|
|
#include "power.h"
|
|
#include "file.h"
|
|
#include "common.h"
|
|
#include "rb-loader.h"
|
|
#include "loader_strerror.h"
|
|
#include "version.h"
|
|
#include "powermgmt.h"
|
|
#include "usb.h"
|
|
#ifdef IPOD_NANO3G
|
|
#include "nand-target.h"
|
|
#endif
|
|
#ifdef NAND_CHECK
|
|
#include "string-extra.h"
|
|
#endif
|
|
#ifdef HAVE_SERIAL
|
|
#include "serial.h"
|
|
#endif
|
|
|
|
#include "s5l87xx.h"
|
|
#include "clocking-s5l8702.h"
|
|
#include "spi-s5l8702.h"
|
|
#include "i2c-s5l8702.h"
|
|
#include "gpio-s5l8702.h"
|
|
#include "pmu-target.h"
|
|
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
|
|
#include "norboot-target.h"
|
|
#endif
|
|
|
|
|
|
#define ERR_RB 0
|
|
#define ERR_OF 1
|
|
#define ERR_STORAGE 2
|
|
#define ERR_LBA28 3
|
|
|
|
/* Safety measure - maximum allowed firmware image size.
|
|
The largest known current (October 2009) firmware is about 6.2MB so
|
|
we set this to 8MB.
|
|
*/
|
|
#define MAX_LOADSIZE (8*1024*1024)
|
|
|
|
#define LCD_RBYELLOW LCD_RGBPACK(255,192,0)
|
|
#define LCD_REDORANGE LCD_RGBPACK(255,70,0)
|
|
#define LCD_GREEN LCD_RGBPACK(0,255,0)
|
|
|
|
extern void bss_init(void);
|
|
extern uint32_t _movestart;
|
|
extern uint32_t start_loc;
|
|
|
|
extern int line;
|
|
|
|
#ifndef S5L87XX_DEVELOPMENT_BOOTLOADER
|
|
#ifdef HAVE_BOOTLOADER_USB_MODE
|
|
static void usb_mode(void)
|
|
{
|
|
int button;
|
|
|
|
verbose = true;
|
|
|
|
printf("Entering USB mode...");
|
|
|
|
powermgmt_init();
|
|
|
|
/* The code will ask for the maximum possible value */
|
|
usb_charging_enable(USB_CHARGING_ENABLE);
|
|
|
|
usb_init();
|
|
usb_start_monitoring();
|
|
|
|
/* Wait until USB is plugged */
|
|
while (usb_detect() != USB_INSERTED)
|
|
{
|
|
printf("Plug USB cable");
|
|
line--;
|
|
sleep(HZ/10);
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
button = button_get_w_tmo(HZ/10);
|
|
|
|
if (button == SYS_USB_CONNECTED)
|
|
break; /* Hit */
|
|
|
|
if (usb_detect() == USB_EXTRACTED)
|
|
break; /* Cable pulled */
|
|
|
|
/* Wait for threads to connect or cable is pulled */
|
|
printf("USB: Connecting...");
|
|
line--;
|
|
}
|
|
|
|
if (button == SYS_USB_CONNECTED)
|
|
{
|
|
/* Got the message - wait for disconnect */
|
|
printf("Bootloader USB mode");
|
|
|
|
/* Ack the SYS_USB_CONNECTED polled from the button queue */
|
|
usb_acknowledge(SYS_USB_CONNECTED_ACK, button_get_data());
|
|
|
|
while(1)
|
|
{
|
|
button = button_get_w_tmo(HZ/2);
|
|
if (button == SYS_USB_DISCONNECTED)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We don't want the HDD to spin up if the USB is attached again */
|
|
usb_close();
|
|
printf("USB mode exit ");
|
|
}
|
|
#endif /* HAVE_BOOTLOADER_USB_MODE */
|
|
|
|
#if defined(NAND_CHECK) && defined(HAVE_BOOTLOADER_USB_MODE) \
|
|
&& !defined(S5L87XX_DEVELOPMENT_BOOTLOADER)
|
|
/* The contributor NAND check (build with -DNAND_CHECK, run from DFU): show
|
|
* what the driver identified and how the read-only mount went, add the
|
|
* unit's model and firmware version, then serve the raw NAND over USB for
|
|
* utils/ipodnano3g/nandcheck/nandcheck.py. Nothing is written to the NAND. */
|
|
/* The bootloader's backlight calls are stubs, and the check's screen has
|
|
* come up too dim to read at the default level. Full brightness draws more
|
|
* than a battery-less unit's USB power alone can supply and can keep it
|
|
* from booting at all, so this stops at the default rather than the max. */
|
|
static void nand_check_light(void)
|
|
{
|
|
backlight_hw_brightness(DEFAULT_BRIGHTNESS_SETTING);
|
|
backlight_hw_on();
|
|
}
|
|
|
|
/* The report's key lines, short enough for the screen; the whole report
|
|
* goes to nandcheck.py */
|
|
static void nand_check_print(const char *report)
|
|
{
|
|
static const char *const keys[] = {
|
|
"banks", "row", "mode", "pagesize", "validated", "ftl", "verdict",
|
|
"model", "swvr",
|
|
};
|
|
static char text[SECTOR_SIZE];
|
|
char *p, *nl, *sp;
|
|
size_t i;
|
|
|
|
strlcpy(text, report, sizeof(text));
|
|
for (p = text; (nl = strchr(p, '\n')); p = nl + 1)
|
|
{
|
|
*nl = '\0';
|
|
sp = strchr(p, ' ');
|
|
if (!sp)
|
|
continue;
|
|
if (!strncmp(p, "ids ", 4))
|
|
{
|
|
/* bank 0's id; the others are in the report */
|
|
printf("id %.8s", sp + 1);
|
|
continue;
|
|
}
|
|
for (i = 0; i < ARRAYLEN(keys); i++)
|
|
if ((size_t)(sp - p) == strlen(keys[i])
|
|
&& !strncmp(p, keys[i], sp - p))
|
|
printf("%s", p);
|
|
}
|
|
}
|
|
|
|
static void nand_check(void)
|
|
{
|
|
static struct SysCfg syscfg;
|
|
char line[64];
|
|
int rc;
|
|
ssize_t n;
|
|
|
|
nand_check_light();
|
|
snprintf(line, sizeof(line), "battery %dmV", _battery_voltage());
|
|
rc = storage_init();
|
|
/* USB mode unmounts every volume when the host configures the device,
|
|
* through the file system's locks and object lists */
|
|
filesystem_init();
|
|
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Nano 3G NAND check");
|
|
lcd_set_foreground(LCD_WHITE);
|
|
printf("%s", line);
|
|
nand_check_note(line);
|
|
nand_check_light();
|
|
if (rc)
|
|
printf("storage_init %d", rc);
|
|
|
|
/* Which unit this is, but not its serial number */
|
|
n = syscfg_read(&syscfg);
|
|
if (n != -1)
|
|
{
|
|
size_t i, count = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
const struct SysCfgEntry *e = &syscfg.entries[i];
|
|
const uint32_t *w = (const uint32_t *)e->data;
|
|
|
|
if (e->tag == SYSCFG_TAG_MODN)
|
|
snprintf(line, sizeof(line), "model %.16s", e->data);
|
|
else if (e->tag == SYSCFG_TAG_SWVR)
|
|
snprintf(line, sizeof(line), "swvr %.16s", e->data);
|
|
else if (e->tag == SYSCFG_TAG_HWVR)
|
|
snprintf(line, sizeof(line), "hwvr %08lx",
|
|
(unsigned long)w[1]);
|
|
else
|
|
continue;
|
|
nand_check_note(line);
|
|
}
|
|
}
|
|
|
|
nand_check_print(nand_check_report());
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Photo, then connect USB");
|
|
printf("and run nandcheck.py");
|
|
lcd_set_foreground(LCD_WHITE);
|
|
/* One USB session: usb_mode() starts power management each time, so it
|
|
* must not run twice */
|
|
usb_mode();
|
|
nand_check_light();
|
|
printf("Done. Hold MENU+SELECT");
|
|
printf("to restart");
|
|
while (1)
|
|
sleep(HZ);
|
|
}
|
|
#endif
|
|
|
|
void fatal_error(int err)
|
|
{
|
|
verbose = true;
|
|
|
|
/* System font is 6 pixels wide */
|
|
line++;
|
|
switch (err)
|
|
{
|
|
case ERR_RB:
|
|
#ifdef HAVE_BOOTLOADER_USB_MODE
|
|
usb_mode();
|
|
printf("Hold MENU+SELECT to reboot");
|
|
break;
|
|
#endif
|
|
case ERR_STORAGE:
|
|
printf("Hold MENU+SELECT to reboot");
|
|
printf("then SELECT+PLAY for disk mode");
|
|
break;
|
|
case ERR_OF:
|
|
printf("Hold MENU+SELECT to reboot");
|
|
printf("and enter Rockbox firmware");
|
|
break;
|
|
case ERR_LBA28:
|
|
printf("Hold MENU+SELECT to reboot");
|
|
printf("and LEFT if you are REALLY sure");
|
|
break;
|
|
}
|
|
|
|
#if (CONFIG_STORAGE & STORAGE_ATA)
|
|
if (ide_powered())
|
|
ata_sleepnow(); /* Immediately spindown the disk. */
|
|
#endif
|
|
|
|
line++;
|
|
lcd_set_foreground(LCD_REDORANGE);
|
|
while (1) {
|
|
lcd_puts(0, line, button_hold() ? "Hold switch on!"
|
|
: " ");
|
|
lcd_update();
|
|
}
|
|
}
|
|
|
|
#if (CONFIG_STORAGE & STORAGE_ATA)
|
|
extern unsigned short battery_level_disksafe;
|
|
static void battery_trap(void)
|
|
{
|
|
int vbat, old_verb;
|
|
int th = 50;
|
|
|
|
old_verb = verbose;
|
|
verbose = true;
|
|
|
|
usb_charging_maxcurrent_change(100);
|
|
|
|
while (1)
|
|
{
|
|
vbat = _battery_voltage();
|
|
|
|
/* Two reasons to use this threshold (may require adjustments):
|
|
* - when USB (or wall adaptor) is plugged/unplugged, Vbat readings
|
|
* differ as much as more than 200 mV when charge current is at
|
|
* maximum (~340 mA).
|
|
* - RB uses some sort of average/compensation for battery voltage
|
|
* measurements, battery icon blinks at battery_level_disksafe,
|
|
* when the HDD is used heavily (large database) the level drops
|
|
* to battery_level_shutoff quickly.
|
|
*/
|
|
if (vbat >= battery_level_disksafe + th)
|
|
break;
|
|
th = 200;
|
|
|
|
if (power_input_status() != POWER_INPUT_NONE) {
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Low battery: %d mV, charging... ", vbat);
|
|
sleep(HZ*3);
|
|
}
|
|
else {
|
|
/* Wait for the user to insert a charger */
|
|
int tmo = 10;
|
|
lcd_set_foreground(LCD_REDORANGE);
|
|
while (1) {
|
|
vbat = _battery_voltage();
|
|
printf("Low battery: %d mV, power off in %d ", vbat, tmo);
|
|
if (!tmo--) {
|
|
/* Raise Vsysok (hyst=0.02*Vsysok) to avoid PMU
|
|
standby<->active looping */
|
|
if (vbat < 3200)
|
|
pmu_write(PCF5063X_REG_SVMCTL, 0xA /*3200mV*/);
|
|
power_off();
|
|
}
|
|
sleep(HZ*1);
|
|
if (power_input_status() != POWER_INPUT_NONE)
|
|
break;
|
|
line--;
|
|
}
|
|
}
|
|
line--;
|
|
}
|
|
|
|
verbose = old_verb;
|
|
lcd_set_foreground(LCD_WHITE);
|
|
printf("Battery status ok: %d mV ", vbat);
|
|
}
|
|
#endif /* CONFIG_STORAGE & STORAGE_ATA */
|
|
#endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */
|
|
|
|
static int launch_onb(int clkdiv)
|
|
{
|
|
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
|
|
/* SPI clock = PClk/(clkdiv+1) */
|
|
spi_clkdiv(SPI_PORT, clkdiv);
|
|
|
|
/* Actually IRAM1_ORIG contains current RB bootloader IM3 header,
|
|
it will be replaced by ONB IM3 header, so this function must
|
|
be called once!!! */
|
|
struct Im3Info *hinfo = (struct Im3Info*)IRAM1_ORIG;
|
|
|
|
/* Loads ONB in IRAM0, exception vector table is destroyed !!! */
|
|
int rc = im3_read(
|
|
NORBOOT_OFF + im3_nor_sz(hinfo), hinfo, (void*)IRAM0_ORIG);
|
|
|
|
if (rc != 0) {
|
|
/* Restore exception vector table */
|
|
memcpy((void*)IRAM0_ORIG, &_movestart, 4*(&start_loc-&_movestart));
|
|
commit_discard_idcache();
|
|
return rc;
|
|
}
|
|
|
|
/* Disable all external interrupts */
|
|
eint_init();
|
|
|
|
commit_discard_idcache();
|
|
|
|
/* Branch to start of IRAM */
|
|
asm volatile("mov pc, %0"::"r"(IRAM0_ORIG));
|
|
while(1);
|
|
#elif defined(IPOD_NANO4G)
|
|
(void) clkdiv;
|
|
|
|
lcd_set_foreground(LCD_REDORANGE);
|
|
printf("Not implemented");
|
|
|
|
line++;
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
while (button_status() != BUTTON_SELECT)
|
|
sleep(HZ/100);
|
|
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* Launch OF when kernel mode is running */
|
|
static int kernel_launch_onb(void)
|
|
{
|
|
disable_irq();
|
|
int rc = launch_onb(3); /* 54/4 = 13.5 MHz. */
|
|
enable_irq();
|
|
return rc;
|
|
}
|
|
|
|
/* The boot sequence is executed on power-on or reset. After power-up
|
|
* the device could come from a state of hibernation, OF hibernates
|
|
* the iPod after an inactive period of ~30 minutes, on this state the
|
|
* SDRAM is in self-refresh mode.
|
|
*
|
|
* t0 = 0
|
|
* S5L8702 BOOTROM loads an IM3 image located at NOR:
|
|
* - IM3 header (first 0x800 bytes) is loaded at IRAM1_ORIG
|
|
* - IM3 body (decrypted RB bootloader) is loaded at IRAM0_ORIG
|
|
* The time needed to load the RB bootloader (~100 Kb) is estimated
|
|
* on 200~250 ms. Once executed, RB booloader moves itself from
|
|
* IRAM0_ORIG to IRAM1_ORIG+0x800, preserving current IM3 header
|
|
* that contains the NOR offset where the ONB (original NOR boot),
|
|
* is located (see dualboot.c for details).
|
|
*
|
|
* t1 = ~250 ms.
|
|
* If the PMU is hibernated, decrypted ONB (size 128Kb) is loaded
|
|
* and executed, it takes ~120 ms. Then the ONB restores the
|
|
* iPod to the state prior to hibernation.
|
|
* If not, initialize system and RB kernel, wait for t2.
|
|
*
|
|
* t2 = ~650 ms.
|
|
* Check user button selection.
|
|
* If OF, diagmode, or diskmode is selected then launch ONB.
|
|
* If not, wait for LCD initialization.
|
|
*
|
|
* t3 = ~700,~900 ms. (lcd_type_01,lcd_type_23)
|
|
* LCD is initialized, baclight ON.
|
|
* Wait for HDD spin-up.
|
|
*
|
|
* t4 = ~2600,~2800 ms.
|
|
* HDD is ready.
|
|
* If hold switch is locked, then load and launch ONB.
|
|
* If not, load rockbox.ipod file from HDD.
|
|
*
|
|
* t5 = ~2800,~3000 ms.
|
|
* rockbox.ipod is executed.
|
|
*/
|
|
|
|
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
|
|
#include "piezo.h"
|
|
#include "lcd-s5l8702.h"
|
|
extern int lcd_type;
|
|
|
|
static uint16_t alive[] = { 500,100,0, 0 };
|
|
static uint16_t alivelcd[] = { 2000,200,0, 0 };
|
|
|
|
#ifdef HAVE_LCD_SLEEP
|
|
static void sleep_test(void)
|
|
{
|
|
int sleep_tmo = 5;
|
|
int awake_tmo = 3;
|
|
|
|
lcd_clear_display();
|
|
lcd_set_foreground(LCD_WHITE);
|
|
line = 0;
|
|
|
|
printf("Entering LCD sleep mode in %d seconds,", sleep_tmo);
|
|
printf("during sleep mode you will see a white");
|
|
printf("screen for about %d seconds.", awake_tmo);
|
|
while (sleep_tmo--) {
|
|
printf("Sleep in %d...", sleep_tmo);
|
|
sleep(HZ*1);
|
|
}
|
|
lcd_sleep();
|
|
sleep(HZ*awake_tmo);
|
|
lcd_awake();
|
|
|
|
line++;
|
|
printf("Awake!");
|
|
|
|
line++;
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
while (button_status() != BUTTON_SELECT)
|
|
sleep(HZ/100);
|
|
}
|
|
#endif
|
|
|
|
static void pmu_info(void)
|
|
{
|
|
int loop = 0;
|
|
|
|
lcd_clear_display();
|
|
lcd_update();
|
|
while (button_status() != BUTTON_NONE);
|
|
|
|
while (1)
|
|
{
|
|
lcd_set_foreground(LCD_WHITE);
|
|
lcd_clear_display();
|
|
line = 0;
|
|
printf("loop: %d", loop++);
|
|
|
|
for (int i = 0; i < 128; i += 8)
|
|
{
|
|
unsigned char buf[8];
|
|
|
|
#if defined(IPOD_NANO3G)
|
|
if (i == 0) {
|
|
static int flip = 0;
|
|
if (flip) {
|
|
pmu_write(6, 0xff);
|
|
pmu_write(7, 0xff);
|
|
}
|
|
else {
|
|
pmu_write(6, 0xe7);
|
|
pmu_write(7, 0xfe);
|
|
}
|
|
flip ^= 1;
|
|
}
|
|
#elif defined(IPOD_NANO4G)
|
|
if (i == 120)
|
|
for (int j = 0; j < 8; j++)
|
|
pmu_write(i+j, j);
|
|
#endif
|
|
for (int j = 0; j < 8; j++)
|
|
buf[j] = pmu_read(i+j);
|
|
|
|
printf(" %2x: %2x %2x %2x %2x %2x %2x %2x %2x", i,
|
|
buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]);
|
|
}
|
|
line++;
|
|
printf("USB: %s ", (usb_detect() == USB_INSERTED) ? "inserted" : "not inserted");
|
|
#if CONFIG_CHARGING
|
|
printf("Firewire: %s ", pmu_firewire_present() ? "inserted" : "not inserted");
|
|
#endif
|
|
#ifdef IPOD_ACCESSORY_PROTOCOL
|
|
printf("Accessory: %s ", pmu_accessory_present() ? "inserted" : "not inserted");
|
|
#endif
|
|
printf("Hold Switch: %s ", pmu_holdswitch_locked() ? "locked" : "unlocked");
|
|
line++;
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
if (button_status() == BUTTON_SELECT)
|
|
break;
|
|
sleep(HZ/2);
|
|
}
|
|
}
|
|
|
|
static void gpio_info(void)
|
|
{
|
|
int loop = 0;
|
|
|
|
lcd_clear_display();
|
|
|
|
while (1)
|
|
{
|
|
lcd_set_foreground(LCD_WHITE);
|
|
lcd_clear_display();
|
|
line = 0;
|
|
printf("loop: %d", loop++);
|
|
for (int i = 0; i < GPIO_N_GROUPS; i ++)
|
|
{
|
|
printf(" %x: %8x %2x %4x %2x %2x", i,
|
|
PCON(i), PDAT(i), PUNA(i), PUNB(i), PUNC(i));
|
|
}
|
|
line++;
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
if (button_status() == BUTTON_SELECT)
|
|
break;
|
|
sleep(HZ/5);
|
|
}
|
|
}
|
|
|
|
static void run_of(void)
|
|
{
|
|
int tmo = 5;
|
|
lcd_clear_display();
|
|
lcd_set_foreground(LCD_WHITE);
|
|
line = 0;
|
|
while (tmo--) {
|
|
printf("Booting OF in %d...", tmo);
|
|
sleep(HZ*1);
|
|
}
|
|
|
|
int rc = kernel_launch_onb();
|
|
printf("Load OF error: %d", rc);
|
|
sleep(HZ*10);
|
|
}
|
|
|
|
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
|
|
static void print_syscfg(void)
|
|
{
|
|
lcd_clear_display();
|
|
lcd_set_foreground(LCD_WHITE);
|
|
line = 0;
|
|
|
|
struct SysCfg syscfg;
|
|
const ssize_t result = syscfg_read(&syscfg);
|
|
|
|
if (result == -1) {
|
|
printf("SCfg magic not found. NOR flash is corrupted.");
|
|
goto end;
|
|
}
|
|
|
|
printf("Total size: %lu bytes, %lu entries", syscfg.header.size, syscfg.header.num_entries);
|
|
|
|
if (result > 0) {
|
|
printf("Wrong size: expected %ld, got %lu", result, syscfg.header.size);
|
|
}
|
|
|
|
if (syscfg.header.num_entries > SYSCFG_MAX_ENTRIES) {
|
|
printf("Too many entries, showing only first %u", SYSCFG_MAX_ENTRIES);
|
|
}
|
|
|
|
const size_t syscfg_num_entries = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES);
|
|
|
|
for (size_t i = 0; i < syscfg_num_entries; i++) {
|
|
const struct SysCfgEntry* entry = &syscfg.entries[i];
|
|
const char* tag = (char *)&entry->tag;
|
|
const uint32_t* data32 = (uint32_t *)entry->data;
|
|
|
|
switch (entry->tag) {
|
|
case SYSCFG_TAG_SRNM:
|
|
printf("Serial number (SrNm): %s", entry->data);
|
|
break;
|
|
case SYSCFG_TAG_FWID:
|
|
printf("Firmware ID (FwId): %07lX", data32[1] & 0x0FFFFFFF);
|
|
break;
|
|
case SYSCFG_TAG_HWID:
|
|
printf("Hardware ID (HwId): %08lX", data32[0]);
|
|
break;
|
|
case SYSCFG_TAG_HWVR:
|
|
printf("Hardware version (HwVr): %06lX", data32[1]);
|
|
break;
|
|
case SYSCFG_TAG_CODC:
|
|
printf("Codec (Codc): %s", entry->data);
|
|
break;
|
|
case SYSCFG_TAG_SWVR:
|
|
printf("Software version (SwVr): %s", entry->data);
|
|
break;
|
|
case SYSCFG_TAG_MLBN:
|
|
printf("Logic board serial number (MLBN): %s", entry->data);
|
|
break;
|
|
case SYSCFG_TAG_MODN:
|
|
printf("Model number (Mod#): %s", entry->data);
|
|
break;
|
|
case SYSCFG_TAG_REGN:
|
|
printf("Sales region (Regn): %08lX %08lX", data32[0], data32[1]);
|
|
break;
|
|
default:
|
|
printf("%c%c%c%c: %08lX %08lX %08lX %08lX",
|
|
tag[3], tag[2], tag[1], tag[0],
|
|
data32[0], data32[1], data32[2], data32[3]
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
end:
|
|
line++;
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
while (button_status() != BUTTON_SELECT)
|
|
sleep(HZ/100);
|
|
}
|
|
|
|
static void print_bootloader_hash(void)
|
|
{
|
|
lcd_clear_display();
|
|
lcd_set_foreground(LCD_WHITE);
|
|
line = 0;
|
|
|
|
struct Im3Info hinfo;
|
|
int rc = im3_read(NORBOOT_OFF, &hinfo, NULL);
|
|
|
|
if (rc != 0) {
|
|
printf("Error loading the primary bootloader: %d", rc);
|
|
goto end;
|
|
}
|
|
|
|
unsigned char primary_hash[SIGN_SZ];
|
|
|
|
memcpy(primary_hash, hinfo.u.enc12.data_sign, SIGN_SZ);
|
|
hwkeyaes(HWKEYAES_DECRYPT, HWKEYAES_UKEY, primary_hash, SIGN_SZ);
|
|
|
|
unsigned bl_nor_sz = im3_nor_sz(&hinfo);
|
|
rc = im3_read(NORBOOT_OFF + bl_nor_sz, &hinfo, NULL);
|
|
|
|
if (rc == 0) {
|
|
// Rockbox bootloader is installed as primary
|
|
// Stock bootloader is backed up
|
|
unsigned char backup_hash[SIGN_SZ];
|
|
memcpy(backup_hash, hinfo.u.enc12.data_sign, SIGN_SZ);
|
|
hwkeyaes(HWKEYAES_DECRYPT, HWKEYAES_UKEY, backup_hash, SIGN_SZ);
|
|
|
|
printf("Rockbox bootloader hash:");
|
|
|
|
for (int i = 0; i < SIGN_SZ; i++) {
|
|
lcd_putsf(i * 2, line, "%02X", primary_hash[i]);
|
|
}
|
|
|
|
line += 2;
|
|
lcd_update();
|
|
|
|
printf("Stock bootloader hash:");
|
|
|
|
for (int i = 0; i < SIGN_SZ; i++) {
|
|
lcd_putsf(i * 2, line, "%02X", backup_hash[i]);
|
|
}
|
|
|
|
line++;
|
|
lcd_update();
|
|
}
|
|
else {
|
|
// Stock bootloader is installed as primary
|
|
// No backup bootloader
|
|
printf("Rockbox bootloader is not installed!");
|
|
line++;
|
|
|
|
printf("Stock bootloader hash:");
|
|
|
|
for (int i = 0; i < SIGN_SZ; i++) {
|
|
lcd_putsf(i * 2, line, "%02X", primary_hash[i]);
|
|
}
|
|
|
|
line++;
|
|
lcd_update();
|
|
}
|
|
|
|
end:
|
|
line++;
|
|
while (button_status() != BUTTON_NONE)
|
|
sleep(HZ/100);
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
while (button_status() != BUTTON_SELECT)
|
|
sleep(HZ/100);
|
|
}
|
|
|
|
#ifdef HAVE_SERIAL
|
|
|
|
#define FLASH_PAGES (FLASH_SIZE >> 12)
|
|
#define FLASH_PAGE_SIZE (FLASH_SIZE >> 8)
|
|
|
|
static void dump_bootflash(void)
|
|
{
|
|
lcd_clear_display();
|
|
lcd_set_foreground(LCD_WHITE);
|
|
line = 0;
|
|
|
|
uint8_t page[FLASH_PAGE_SIZE];
|
|
printf("Total pages: %d", FLASH_PAGES);
|
|
|
|
bootflash_init(SPI_PORT);
|
|
|
|
for (int i = 0; i < FLASH_PAGES; i++) {
|
|
printf("Reading flash... %d", i + 1);
|
|
bootflash_read(SPI_PORT, i << 12, FLASH_PAGE_SIZE, page);
|
|
|
|
printf("Sending over UART... %d", i + 1);
|
|
serial_tx_raw(page, FLASH_PAGE_SIZE);
|
|
line -= 2;
|
|
}
|
|
|
|
bootflash_close(SPI_PORT);
|
|
|
|
line += 2;
|
|
printf("Done!");
|
|
piezo_seq(alive);
|
|
|
|
line++;
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("Press SELECT to continue");
|
|
while (button_status() != BUTTON_SELECT)
|
|
sleep(HZ/100);
|
|
}
|
|
#endif /* HAVE_SERIAL */
|
|
#endif /* IPOD_6G || IPOD_NANO3G */
|
|
|
|
static void devel_menu(void)
|
|
{
|
|
const char *items[] = {
|
|
#ifdef HAVE_LCD_SLEEP
|
|
"LCD sleep/awake test",
|
|
#endif
|
|
"PMU info",
|
|
"GPIO info",
|
|
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
|
|
"Show SysCfg",
|
|
"Show bootloader hash",
|
|
#ifdef HAVE_SERIAL
|
|
"Dump bootflash to UART",
|
|
#endif
|
|
#endif
|
|
"Launch OF",
|
|
//"Launch Rockbox",
|
|
"Restart",
|
|
"Power off",
|
|
};
|
|
void (*handlers[])(void) = {
|
|
#ifdef HAVE_LCD_SLEEP
|
|
sleep_test,
|
|
#endif
|
|
pmu_info,
|
|
gpio_info,
|
|
#if defined(IPOD_6G) || defined(IPOD_NANO3G)
|
|
print_syscfg,
|
|
print_bootloader_hash,
|
|
#ifdef HAVE_SERIAL
|
|
dump_bootflash,
|
|
#endif
|
|
#endif
|
|
run_of,
|
|
//run_rockbox,
|
|
system_reboot,
|
|
power_off,
|
|
};
|
|
const size_t items_count = sizeof(items) / sizeof(items[0]);
|
|
unsigned char selected_item = 0;
|
|
|
|
while (1)
|
|
{
|
|
lcd_clear_display();
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
line = 0;
|
|
printf("Development menu");
|
|
|
|
for (size_t i = 0; i < items_count; i++) {
|
|
lcd_set_foreground(i == selected_item ? LCD_GREEN : LCD_WHITE);
|
|
printf(items[i]);
|
|
}
|
|
|
|
while (button_status() != BUTTON_NONE);
|
|
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
switch (button_status())
|
|
{
|
|
case BUTTON_MENU:
|
|
case BUTTON_LEFT:
|
|
if (selected_item > 0) {
|
|
selected_item--;
|
|
done = true;
|
|
}
|
|
else {
|
|
sleep(HZ/100);
|
|
}
|
|
break;
|
|
case BUTTON_PLAY:
|
|
case BUTTON_RIGHT:
|
|
if (selected_item < items_count - 1) {
|
|
selected_item++;
|
|
done = true;
|
|
}
|
|
else {
|
|
sleep(HZ/100);
|
|
}
|
|
break;
|
|
case BUTTON_SELECT:
|
|
handlers[selected_item]();
|
|
done = true;
|
|
break;
|
|
default:
|
|
sleep(HZ/100);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */
|
|
|
|
void main(void)
|
|
{
|
|
int rc = 0;
|
|
|
|
usec_timer_init();
|
|
|
|
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
|
|
piezo_seq(alive);
|
|
#endif
|
|
|
|
/* Configure I2C0 */
|
|
i2c_preinit(0);
|
|
|
|
if (pmu_is_hibernated()) {
|
|
rc = launch_onb(1); /* 27/2 = 13.5 MHz. */
|
|
}
|
|
|
|
system_preinit();
|
|
memory_init();
|
|
/*
|
|
* XXX: BSS is initialized here, do not use .bss before this line
|
|
*/
|
|
bss_init();
|
|
|
|
system_init();
|
|
kernel_init();
|
|
i2c_init();
|
|
power_init();
|
|
|
|
enable_irq();
|
|
|
|
#ifdef HAVE_SERIAL
|
|
serial_setup();
|
|
#endif
|
|
|
|
button_init();
|
|
if (rc == 0) {
|
|
/* User button selection timeout */
|
|
while (USEC_TIMER < 400000);
|
|
int btn = button_read_device();
|
|
/* This prevents HDD spin-up when the user enters DFU */
|
|
if (btn == (BUTTON_SELECT|BUTTON_MENU)) {
|
|
while (button_read_device() == (BUTTON_SELECT|BUTTON_MENU))
|
|
sleep(HZ/10);
|
|
sleep(HZ);
|
|
btn = button_read_device();
|
|
}
|
|
/* Enter OF, diagmode and diskmode using ONB */
|
|
if ((btn == BUTTON_MENU)
|
|
|| (btn == (BUTTON_SELECT|BUTTON_LEFT))
|
|
|| (btn == (BUTTON_SELECT|BUTTON_PLAY))) {
|
|
rc = kernel_launch_onb();
|
|
}
|
|
}
|
|
|
|
lcd_init();
|
|
lcd_set_foreground(LCD_WHITE);
|
|
lcd_set_background(LCD_BLACK);
|
|
lcd_clear_display();
|
|
font_init();
|
|
lcd_setfont(FONT_SYSFIXED);
|
|
|
|
// TODO: see if removing this causes the nano3g LCD to initialize properly
|
|
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
|
|
sleep(HZ);
|
|
for (int i = 0; i < lcd_type+1; i++) {
|
|
sleep(HZ/2);
|
|
piezo_seq(alivelcd);
|
|
}
|
|
#endif
|
|
|
|
lcd_update();
|
|
sleep(HZ/40); /* wait for lcd update */
|
|
|
|
verbose = true;
|
|
|
|
printf("Rockbox boot loader");
|
|
printf("Version: %s", rbversion);
|
|
|
|
backlight_init(); /* Turns on the backlight */
|
|
|
|
#ifdef S5L87XX_DEVELOPMENT_BOOTLOADER
|
|
line++;
|
|
printf("lcd type: %d", lcd_type);
|
|
#ifdef S5L_LCD_WITH_READID
|
|
extern unsigned char lcd_id[4];
|
|
uint32_t* lcd_id_32 = (uint32_t *)lcd_id;
|
|
printf("lcd id: 0x%x", *lcd_id_32);
|
|
#endif
|
|
#ifdef IPOD_NANO4G
|
|
printf("boot cfg: 0x%x", pmu_read(0x7f));
|
|
#endif
|
|
line++;
|
|
printf("Press SELECT to continue");
|
|
while (button_status() != BUTTON_SELECT)
|
|
sleep(HZ/100);
|
|
|
|
devel_menu();
|
|
#endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */
|
|
|
|
#if defined(NAND_CHECK) && defined(HAVE_BOOTLOADER_USB_MODE) \
|
|
&& !defined(S5L87XX_DEVELOPMENT_BOOTLOADER)
|
|
nand_check();
|
|
#endif
|
|
|
|
#ifndef S5L87XX_DEVELOPMENT_BOOTLOADER
|
|
if (rc == 0) {
|
|
#if (CONFIG_STORAGE & STORAGE_ATA)
|
|
/* Wait until there is enought power to spin-up HDD */
|
|
battery_trap();
|
|
#endif
|
|
|
|
rc = storage_init();
|
|
#ifdef IPOD_NANO3G
|
|
if (rc == NAND_ERR_UNSUPPORTED) {
|
|
/* Rockbox only drives NAND chips proven on hardware. Say which
|
|
* one this is and how to get it validated, then leave the unit
|
|
* to Apple's firmware, which is untouched. */
|
|
lcd_set_foreground(LCD_RBYELLOW);
|
|
printf("NAND not supported yet");
|
|
lcd_set_foreground(LCD_WHITE);
|
|
printf("chip %08lx x %u", (unsigned long)nand_get_id(),
|
|
nand_get_bank_count());
|
|
printf("Rockbox does not write to");
|
|
printf("chips it has not been");
|
|
printf("tested on.");
|
|
printf("Starting Apple firmware...");
|
|
sleep(8 * HZ); /* long enough to read the message */
|
|
rc = kernel_launch_onb();
|
|
/* Only reached if the ONB could not be read from NOR */
|
|
printf("Apple firmware failed: %d", rc);
|
|
printf("Hold MENU+SELECT to reboot,");
|
|
printf("then SELECT+PLAY for disk mode");
|
|
while (1)
|
|
sleep(HZ);
|
|
}
|
|
#endif
|
|
if (rc != 0) {
|
|
printf("Storage error: %d", rc);
|
|
fatal_error(ERR_STORAGE);
|
|
}
|
|
|
|
filesystem_init();
|
|
|
|
/* We wait until HDD spins up to check for hold button */
|
|
if (button_hold()) {
|
|
#ifdef SYSCFG_MAX_ENTRIES
|
|
bool lba48 = false;
|
|
struct SysCfg syscfg;
|
|
const ssize_t result = syscfg_read(&syscfg);
|
|
if (result != -1) {
|
|
const size_t syscfg_num_entries = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES);
|
|
for (size_t i = 0; i < syscfg_num_entries; i++) {
|
|
const struct SysCfgEntry* entry = &syscfg.entries[i];
|
|
const uint32_t* data32 = (uint32_t *)entry->data;
|
|
if (entry->tag == SYSCFG_TAG_HWVR) {
|
|
lba48 = (data32[1] >= 0x130200);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int btn = button_read_device();
|
|
|
|
struct storage_info sinfo;
|
|
storage_get_info(0, &sinfo);
|
|
if (sinfo.num_sectors < (1 << 28) || lba48 || btn & BUTTON_LEFT) {
|
|
printf("Executing OF...");
|
|
#if (CONFIG_STORAGE & STORAGE_ATA)
|
|
ata_sleepnow();
|
|
#endif
|
|
rc = kernel_launch_onb();
|
|
} else {
|
|
printf("OF does not support LBA48");
|
|
fatal_error(ERR_LBA28);
|
|
}
|
|
}
|
|
#else
|
|
printf("Executing OF...");
|
|
#if (CONFIG_STORAGE & STORAGE_ATA)
|
|
ata_sleepnow();
|
|
#endif
|
|
rc = kernel_launch_onb();
|
|
#endif /* SYSCFG_MAX_ENTRIES */
|
|
}
|
|
}
|
|
|
|
if (rc != 0) {
|
|
printf("Load OF error: %d", rc);
|
|
fatal_error(ERR_OF);
|
|
}
|
|
|
|
#ifdef HAVE_BOOTLOADER_USB_MODE
|
|
/* Enter USB mode if SELECT+RIGHT are pressed */
|
|
if (button_read_device() == (BUTTON_SELECT|BUTTON_RIGHT)) {
|
|
#if defined(MAX_VIRT_SECTOR_SIZE) && defined(DEFAULT_VIRT_SECTOR_SIZE)
|
|
#ifdef HAVE_MULTIDRIVE
|
|
for (int i = 0 ; i < NUM_DRIVES ; i++)
|
|
#endif
|
|
disk_set_sector_multiplier(IF_MD(i,) DEFAULT_VIRT_SECTOR_SIZE/SECTOR_SIZE);
|
|
#endif
|
|
usb_mode();
|
|
}
|
|
#endif
|
|
|
|
rc = disk_mount_all();
|
|
if (rc <= 0) {
|
|
#ifdef STORAGE_GET_INFO
|
|
struct storage_info sinfo;
|
|
storage_get_info(0, &sinfo);
|
|
#ifdef MAX_PHYS_SECTOR_SIZE
|
|
printf("id: '%s' s:%u*%u", sinfo.product, sinfo.sector_size, sinfo.phys_sector_mult);
|
|
#else
|
|
printf("id: '%s' s:%u", sinfo.product, sinfo.sector_size);
|
|
#endif
|
|
#endif
|
|
struct partinfo pinfo;
|
|
printf("No partition found");
|
|
for (int i = 0 ; i < NUM_VOLUMES ; i++) {
|
|
disk_partinfo(i, &pinfo);
|
|
if (pinfo.type)
|
|
printf("P%d T%02x S%llx",
|
|
i, pinfo.type, (unsigned long long)pinfo.size);
|
|
}
|
|
#if defined(IPOD_NANO3G) && defined(DEFAULT_VIRT_SECTOR_SIZE)
|
|
/* Nothing mounted: show USB hosts Apple's 4096-byte sectors, which
|
|
* its partition table counts in */
|
|
disk_set_sector_multiplier(IF_MD(0,)
|
|
DEFAULT_VIRT_SECTOR_SIZE / SECTOR_SIZE);
|
|
#endif
|
|
fatal_error(ERR_RB);
|
|
}
|
|
|
|
printf("Loading Rockbox...");
|
|
unsigned char *loadbuffer = (unsigned char *)DRAM_ORIG;
|
|
rc = load_firmware(loadbuffer, BOOTFILE, MAX_LOADSIZE);
|
|
|
|
if (rc <= EFILE_EMPTY) {
|
|
printf("Error!");
|
|
printf("Can't load " BOOTFILE ": ");
|
|
printf(loader_strerror(rc));
|
|
fatal_error(ERR_RB);
|
|
}
|
|
|
|
printf("Rockbox loaded.");
|
|
|
|
/* If we get here, we have a new firmware image at 0x08000000, run it */
|
|
disable_irq();
|
|
|
|
int (*kernel_entry)(void) = (void*)loadbuffer;
|
|
commit_discard_idcache();
|
|
rc = kernel_entry();
|
|
|
|
/* End stop - should not get here */
|
|
enable_irq();
|
|
printf("ERR: Failed to boot");
|
|
while(1);
|
|
#endif
|
|
}
|