diff options
author | Amaury Pouly <amaury.pouly@gmail.com> | 2017-02-23 11:33:19 +0100 |
---|---|---|
committer | Amaury Pouly <amaury.pouly@gmail.com> | 2017-09-05 21:42:12 +0200 |
commit | 1d121e8c082fe67757cf0d4df7b9e6ca1e26f755 (patch) | |
tree | 1c93842d99bb8e4a5f3ed5bca38c05e7f17894fa /bootloader | |
parent | 142f80f07d96305f1618c99c28c13319b1b279e6 (diff) | |
download | rockbox-1d121e8c082fe67757cf0d4df7b9e6ca1e26f755.tar.gz rockbox-1d121e8c082fe67757cf0d4df7b9e6ca1e26f755.tar.bz2 rockbox-1d121e8c082fe67757cf0d4df7b9e6ca1e26f755.zip |
Initial commit for the Sony NWZ linux port
SUPPORTED SERIES:
- NWZ-E450
- NWZ-E460
- NWZ-E470
- NWZ-E580
- NWZ-A10
NOTES:
- bootloader makefile convert an extra font to be installed alongside the bootloader
since sysfont is way too small
- the toolsicon bitmap comes from the Oxygen iconset
- touchscreen driver is untested
TODO:
- implement audio routing driver (pcm is handled by pcm-alsa)
- fix playback: it crashes on illegal instruction in DEBUG builds
- find out why the browser starts at / instead of /contents
- implement radio support
- implement return to OF for usb handling
- calibrate battery curve (NB: of can report a battery level on a 0-5 scale but
probabl don't want to use that ?)
- implement simulator build (we need a nice image of the player)
- figure out if we can detect jack removal
POTENTIAL TODOS:
- try to build a usb serial gadget and gdbserver
Change-Id: Ic77d71e0651355d47cc4e423a40fb64a60c69a80
Diffstat (limited to 'bootloader')
-rw-r--r-- | bootloader/SOURCES | 2 | ||||
-rw-r--r-- | bootloader/common.c | 2 | ||||
-rw-r--r-- | bootloader/common.h | 2 | ||||
-rw-r--r-- | bootloader/nwz_linux.c | 524 |
4 files changed, 530 insertions, 0 deletions
diff --git a/bootloader/SOURCES b/bootloader/SOURCES index a9c6aebd37..88b8aa03d2 100644 --- a/bootloader/SOURCES +++ b/bootloader/SOURCES @@ -71,6 +71,8 @@ main-pp.c show_logo.c #elif defined(MPIO_HD200) || defined(MPIO_HD300) mpio_hd200_hd300.c +#elif defined(SONY_NWZ_LINUX) +nwz_linux.c #elif defined(RK27_GENERIC) || defined(HM60X) || defined(HM801) \ || defined(MA9) || defined(MA9C) || defined(MA8) || defined(MA8C) \ || defined(IHIFI760) || defined(IHIFI960) diff --git a/bootloader/common.c b/bootloader/common.c index 46c0e3441f..4969d92b40 100644 --- a/bootloader/common.c +++ b/bootloader/common.c @@ -60,6 +60,7 @@ bool verbose = false; bool verbose = true; #endif +#if !(CONFIG_PLATFORM & PLATFORM_HOSTED) int line = 0; #ifdef HAVE_REMOTE_LCD int remote_line = 0; @@ -101,6 +102,7 @@ int printf(const char *format, ...) #endif return len; } +#endif void error(int errortype, int error, bool shutdown) { diff --git a/bootloader/common.h b/bootloader/common.h index d09ff1a5ab..56cbbb335a 100644 --- a/bootloader/common.h +++ b/bootloader/common.h @@ -30,8 +30,10 @@ extern bool verbose; #define EBOOTFILE -3 /* Functions common to all bootloaders */ +#if !(CONFIG_PLATFORM & PLATFORM_HOSTED) void reset_screen(void); int printf(const char *format, ...); +#endif void error(int errortype, int error, bool shutdown); int load_raw_firmware(unsigned char* buf, char* firmware, int buffer_size); #ifdef ROCKBOX_HAS_LOGF diff --git a/bootloader/nwz_linux.c b/bootloader/nwz_linux.c new file mode 100644 index 0000000000..7fe635bf76 --- /dev/null +++ b/bootloader/nwz_linux.c @@ -0,0 +1,524 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2016 by Amaury Pouly + * + * Based on Rockbox iriver bootloader by Linus Nielsen Feltzing + * and the ipodlinux bootloader by Daniel Palffy and Bernard Leach + * + * 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 "lcd.h" +#include "backlight.h" +#include "button-target.h" +#include "button.h" +#include "../kernel/kernel-internal.h" +#include "core_alloc.h" +#include "filesystem-app.h" +#include "nvp-nwz.h" +#include "power-nwz.h" +#include "lcd.h" +#include "font.h" +#include "power.h" +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/wait.h> +#include <stdarg.h> + +/* all images must have the following size */ +#define ICON_WIDTH 130 +#define ICON_HEIGHT 130 + +/* images */ +#include "bitmaps/rockboxicon.h" +#include "bitmaps/toolsicon.h" + +/* don't issue an error when parsing the file for dependencies */ +#if defined(BMPWIDTH_rockboxicon) && (BMPWIDTH_rockboxicon != ICON_WIDTH || \ + BMPHEIGHT_rockboxicon != ICON_HEIGHT) +#error rockboxicon has the wrong resolution +#endif +#if defined(BMPWIDTH_toolsicon) && (BMPWIDTH_toolsicon != ICON_WIDTH || \ + BMPHEIGHT_toolsicon != ICON_HEIGHT) +#error toolsicon has the wrong resolution +#endif + +/* buffer for Sony image, filled from NVP */ +unsigned short sonyicon[ICON_WIDTH * ICON_HEIGHT]; +const struct bitmap bm_sonyicon = +{ + .width = ICON_WIDTH, + .height = ICON_HEIGHT, + .format = FORMAT_NATIVE, + .data = (unsigned char*)sonyicon +}; + +/* return icon y position (x is always centered) */ +int get_icon_y(void) +{ + /* adjust so that this contains the Sony logo and produces a nice logo + * when used with rockbox */ + if(LCD_HEIGHT == 320) + return 70; + else if(LCD_HEIGHT == 400) + return 100; + else + return LCD_HEIGHT / 2 - ICON_HEIGHT + 30; /* guess, probably won't work */ +} + +/* Sony logo extraction */ +bool extract_sony_logo(void) +{ + /* load the entire image from the nvp */ + int bti_size = nwz_nvp_read(NWZ_NVP_BTI, NULL); + if(bti_size < 0) + return false; + unsigned short *bti = malloc(bti_size); + if(nwz_nvp_read(NWZ_NVP_BTI, bti) != bti_size) + return false; + /* compute the offset in the image of the logo itself */ + int x_off = (LCD_WIDTH - ICON_WIDTH) / 2; /* logo is centered horizontally */ + int y_off = get_icon_y(); + /* extract part of the image */ + for(int y = 0; y < ICON_HEIGHT; y++) + { + memcpy(sonyicon + ICON_WIDTH * y, + bti + LCD_WIDTH * (y + y_off) + x_off, ICON_WIDTH * sizeof(unsigned short)); + } + free(bti); + return true; +} + +/* Important Note: this bootloader is carefully written so that in case of + * error, the OF is run. This seems like the safest option since the OF is + * always there and might do magic things. */ + +enum boot_mode +{ + BOOT_ROCKBOX, + BOOT_TOOLS, + BOOT_OF, + BOOT_COUNT, + BOOT_USB, /* special */ + BOOT_STOP, /* power down/suspend */ +}; + +static void display_text_center(int y, const char *text) +{ + int width; + lcd_getstringsize(text, &width, NULL); + lcd_putsxy(LCD_WIDTH / 2 - width / 2, y, text); +} + +static void display_text_centerf(int y, const char *format, ...) +{ + char buf[1024]; + va_list ap; + va_start(ap, format); + + vsnprintf(buf, sizeof(buf), format, ap); + display_text_center(y, buf); +} + +/* get timeout before taking action if the user doesn't touch the device */ +int get_inactivity_tmo(void) +{ + if(button_hold()) + return 5 * HZ; /* Inactivity timeout when on hold */ + else + return 10 * HZ; /* Inactivity timeout when not on hold */ +} + +/* return action on idle timeout */ +enum boot_mode inactivity_action(enum boot_mode cur_selection) +{ + if(button_hold()) + return BOOT_STOP; /* power down/suspend */ + else + return cur_selection; /* return last choice */ +} + +/* we store the boot mode in a file in /tmp so we can reload it between 'boots' + * (since the mostly suspends instead of powering down) */ +enum boot_mode load_boot_mode(enum boot_mode mode) +{ + int fd = open("/tmp/rb_bl_mode.txt", O_RDONLY); + if(fd >= 0) + { + read(fd, &mode, sizeof(mode)); + close(fd); + } + return mode; +} + +void save_boot_mode(enum boot_mode mode) +{ + int fd = open("/tmp/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC); + if(fd >= 0) + { + write(fd, &mode, sizeof(mode)); + close(fd); + } +} + +enum boot_mode get_boot_mode(void) +{ + /* load previous mode, or start with rockbox if none */ + enum boot_mode init_mode = load_boot_mode(BOOT_ROCKBOX); + /* wait for user action */ + enum boot_mode mode = init_mode; + int last_activity = current_tick; + bool hold_status = button_hold(); + while(true) + { + /* on usb detect, return to usb + * FIXME this is a hack, we need proper usb detection */ + if(power_input_status() & POWER_INPUT_USB_CHARGER) + { + /* save last choice */ + save_boot_mode(mode); + return BOOT_USB; + } + /* inactivity detection */ + int timeout = last_activity + get_inactivity_tmo(); + if(TIME_AFTER(current_tick, timeout)) + { + /* save last choice */ + save_boot_mode(mode); + return inactivity_action(mode); + } + /* redraw */ + lcd_clear_display(); + /* display top text */ + if(button_hold()) + { + lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); + display_text_center(0, "ON HOLD!"); + } + else + { + lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); + display_text_center(0, "SELECT PLAYER"); + } + lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); + /* display icon */ + const struct bitmap *icon = (mode == BOOT_OF) ? &bm_sonyicon : + (mode == BOOT_ROCKBOX) ? &bm_rockboxicon : &bm_toolsicon; + lcd_bmp(icon, (LCD_WIDTH - ICON_WIDTH) / 2, get_icon_y()); + /* display bottom description */ + const char *desc = (mode == BOOT_OF) ? "SONY" : + (mode == BOOT_ROCKBOX) ? "ROCKBOX" : "TOOLS"; + display_text_center(get_icon_y() + ICON_HEIGHT + 30, desc); + /* display arrows */ + int arrow_width, arrow_height; + lcd_getstringsize("<", &arrow_width, &arrow_height); + int arrow_y = get_icon_y() + ICON_HEIGHT / 2 - arrow_height / 2; + lcd_putsxy(arrow_width / 2, arrow_y, "<"); + lcd_putsxy(LCD_WIDTH - 3 * arrow_width / 2, arrow_y, ">"); + + lcd_set_foreground(LCD_RGBPACK(0, 255, 0)); + display_text_centerf(LCD_HEIGHT - arrow_height * 3 / 2, "timeout in %d sec", + (timeout - current_tick + HZ - 1) / HZ); + + lcd_update(); + + /* wait for a key */ + int btn = button_get_w_tmo(HZ / 10); + /* record action, changing HOLD counts as action */ + if(btn & BUTTON_MAIN || hold_status != button_hold()) + last_activity = current_tick; + hold_status = button_hold(); + /* ignore release, allow repeat */ + if(btn & BUTTON_REL) + continue; + if(btn & BUTTON_REPEAT) + btn &= ~BUTTON_REPEAT; + /* play -> stop loop and return mode */ + if(btn == BUTTON_PLAY) + break; + /* left/right/up/down: change mode */ + if(btn == BUTTON_LEFT || btn == BUTTON_DOWN) + mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT; + if(btn == BUTTON_RIGHT || btn == BUTTON_UP) + mode = (mode + 1) % BOOT_COUNT; + } + + /* save mode */ + save_boot_mode(mode); + return mode; +} + +void error_screen(const char *msg) +{ + lcd_clear_display(); + lcd_putsf(0, 0, msg); + lcd_update(); +} + +void create_sony_logo(void) +{ + for(int y = 0; y < ICON_HEIGHT; y++) + for(int x = 0; x < ICON_WIDTH; x++) + sonyicon[y * ICON_WIDTH + x] = 0xf81f; +} + +int choice_screen(const char *title, bool center, int nr_choices, const char *choices[]) +{ + int choice = 0; + int max_len = 0; + int h; + lcd_getstringsize("x", NULL, &h); + for(int i = 0; i < nr_choices; i++) + { + int len = strlen(choices[i]); + if(len > max_len) + max_len = len; + } + char *buf = malloc(max_len + 10); + int top_y = 2 * h; + int nr_lines = (LCD_HEIGHT - top_y) / h; + while(true) + { + /* make sure choice is visible */ + int offset = choice - nr_lines / 2; + if(offset < 0) + offset = 0; + lcd_clear_display(); + /* display top text */ + lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); + display_text_center(0, title); + int line = 0; + for(int i = 0; i < nr_choices && line < nr_lines; i++) + { + if(i < offset) + continue; + if(i == choice) + lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); + else + lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); + sprintf(buf, "%s", choices[i]); + if(center) + display_text_center(top_y + h * line, buf); + else + lcd_putsxy(0, top_y + h * line, buf); + line++; + } + + lcd_update(); + + /* wait for a key */ + int btn = button_get_w_tmo(HZ / 10); + /* ignore release, allow repeat */ + if(btn & BUTTON_REL) + continue; + if(btn & BUTTON_REPEAT) + btn &= ~BUTTON_REPEAT; + /* play -> stop loop and return mode */ + if(btn == BUTTON_PLAY || btn == BUTTON_BACK) + { + free(buf); + return btn == BUTTON_PLAY ? choice : -1; + } + /* left/right/up/down: change mode */ + if(btn == BUTTON_LEFT || btn == BUTTON_UP) + choice = (choice + nr_choices - 1) % nr_choices; + if(btn == BUTTON_RIGHT || btn == BUTTON_DOWN) + choice = (choice + 1) % nr_choices; + } +} + +void run_file(const char *name) +{ + char *dirname = "/contents/"; + char *buf = malloc(strlen(dirname) + strlen(name) + 1); + sprintf(buf, "%s%s", dirname, name); + + lcd_clear_display(); + lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); + lcd_putsf(0, 0, "Running %s", name); + lcd_update(); + + pid_t pid = fork(); + if(pid == 0) + { + execlp("sh", "sh", buf, NULL); + _exit(42); + } + int status; + waitpid(pid, &status, 0); + if(WIFEXITED(status)) + { + lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); + lcd_putsf(0, 1, "program returned %d", WEXITSTATUS(status)); + } + else + { + lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); + lcd_putsf(0, 1, "an error occured: %x", status); + } + lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); + lcd_putsf(0, 3, "Press any key or wait"); + lcd_update(); + /* wait a small time */ + sleep(HZ); + /* ignore event */ + while(button_get(false) != 0) {} + /* wait for any key or timeout */ + button_get_w_tmo(4 * HZ); +} + +void run_script_menu(void) +{ + const char **entries = NULL; + int nr_entries = 0; + DIR *dir = opendir("/contents/"); + struct dirent *ent; + while((ent = readdir(dir))) + { + if(ent->d_type != DT_REG) + continue; + entries = realloc(entries, (nr_entries + 1) * sizeof(const char *)); + entries[nr_entries++] = strdup(ent->d_name); + } + closedir(dir); + int idx = choice_screen("RUN SCRIPT", false, nr_entries, entries); + if(idx >= 0) + run_file(entries[idx]); + for(int i = 0; i < nr_entries; i++) + free((char *)entries[i]); + free(entries); +} + +void tools_screen(void) +{ + const char *choices[] = {"Service menu", "Run script", "Restart", "Shutdown"}; + int choice = choice_screen("TOOLS MENU", true, 4, choices); + if(choice == 0) + { + /* run service menu */ + fflush(stdout); + execl("/usr/local/bin/mptapp", "mptapp", NULL); + error_screen("Cannot boot service menu"); + sleep(5 * HZ); + } + else if(choice == 1) + run_script_menu(); + else if(choice == 2) + nwz_power_restart(); + else if(choice == 3) + nwz_power_shutdown(); +} + +/* open log file */ +int open_log(void) +{ + /* open regular log file */ + int fd = open("/contents/rockbox.log", O_RDWR | O_CREAT | O_APPEND); + /* get its size */ + struct stat stat; + if(fstat(fd, &stat) != 0) + return fd; /* on error, don't do anything */ + /* if file is too large, rename it and start a new log file */ + if(stat.st_size < 1000000) + return fd; + close(fd); + /* move file */ + rename("/contents/rockbox.log", "/contents/rockbox.log.1"); + /* re-open the file, truncate in case the move was unsuccessful */ + return open("/contents/rockbox.log", O_RDWR | O_CREAT | O_APPEND | O_TRUNC); +} + +int main(int argc, char **argv) +{ + (void) argc; + (void) argv; + /* redirect stdout and stderr to have error messages logged somewhere on the + * user partition */ + int fd = open_log(); + if(fd >= 0) + { + dup2(fd, fileno(stdout)); + dup2(fd, fileno(stderr)); + close(fd); + } + system_init(); + core_allocator_init(); + kernel_init(); + paths_init(); + lcd_init(); + font_init(); + button_init(); + backlight_init(); + backlight_set_brightness(DEFAULT_BRIGHTNESS_SETTING); + /* try to load the extra font we install on the device */ + int font_id = font_load("/usr/local/share/rockbox/bootloader.fnt"); + if(font_id >= 0) + lcd_setfont(font_id); + /* extract logo */ + if(!extract_sony_logo()) + create_sony_logo(); + /* run all tools menu */ + while(true) + { + enum boot_mode mode = get_boot_mode(); + if(mode == BOOT_USB || mode == BOOT_OF) + { + fflush(stdout); + fflush(stderr); + close(fileno(stdout)); + close(fileno(stderr)); + /* for now the only way we have to trigger USB mode it to run the OF */ + /* boot OF */ + execvp("/usr/local/bin/SpiderApp.of", argv); + error_screen("Cannot boot OF"); + sleep(5 * HZ); + } + else if(mode == BOOT_TOOLS) + { + tools_screen(); + } + else if(mode == BOOT_ROCKBOX) + { + /* Rockbox expects /.rockbox to contain themes, rocks, etc, but we + * cannot easily create this symlink because the root filesystem is + * mounted read-only. Although we could remount it read-write temporarily, + * this is neededlessly complicated and we defer this job to the dualboot + * install script */ + fflush(stdout); + execl("/contents/.rockbox/rockbox.sony", "rockbox.sony", NULL); + printf("execvp failed: %s\n", strerror(errno)); + /* fallback to OF in case of failure */ + error_screen("Cannot boot Rockbox"); + sleep(5 * HZ); + } + else + { + /* FIXME doesn't seem to work */ + printf("suspend\n"); + nwz_power_suspend(); + } + } + /* if we reach this point, everything failed, so return an error so that + * sysmgrd knows something is wrong */ + return 1; +} |