From d55680993df9b6743506814d98b5cc1859828f8a Mon Sep 17 00:00:00 2001 From: Marcin Bukat Date: Thu, 27 Apr 2017 11:36:40 +0200 Subject: Agptek Rocker: Initial commit Change-Id: I26b51106c7b1c36a603fba6d521e917d79b5a95b --- bootloader/rocker_linux.c | 546 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 546 insertions(+) create mode 100644 bootloader/rocker_linux.c (limited to 'bootloader/rocker_linux.c') diff --git a/bootloader/rocker_linux.c b/bootloader/rocker_linux.c new file mode 100644 index 0000000000..dd912ee378 --- /dev/null +++ b/bootloader/rocker_linux.c @@ -0,0 +1,546 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * + * Copyright (C) 2016 by Amaury Pouly + * 2018 by Marcin Bukat + * + * 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 "lcd.h" +#include "font.h" +#include "power.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" + +/* all images must have the following size */ +#define ICON_WIDTH 70 +#define ICON_HEIGHT 70 + +/* images */ +#include "bitmaps/rockboxicon.h" +#include "bitmaps/hibyicon.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_hibyicon) && (BMPWIDTH_hibyicon != ICON_WIDTH || \ + BMPHEIGHT_hibyicon != ICON_HEIGHT) +#error hibyicon has the wrong resolution +#endif +#if defined(BMPWIDTH_toolsicon) && (BMPWIDTH_toolsicon != ICON_WIDTH || \ + BMPHEIGHT_toolsicon != ICON_HEIGHT) +#error toolsicon has the wrong resolution +#endif + +#ifndef BUTTON_REW +#define BUTTON_REW BUTTON_LEFT +#endif +#ifndef BUTTON_FF +#define BUTTON_FF BUTTON_RIGHT +#endif +#ifndef BUTTON_PLAY +#define BUTTON_PLAY BUTTON_SELECT +#endif + +/* return icon y position (x is always centered) */ +static int get_icon_y(void) +{ + int h; + lcd_getstringsize("X", NULL, &h); + return ((LCD_HEIGHT - ICON_HEIGHT)/2) - h; +} + +/* 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 */ +static int get_inactivity_tmo(void) +{ +#if defined(HAS_BUTTON_HOLD) + if(button_hold()) + return 5 * HZ; /* Inactivity timeout when on hold */ + else +#endif + return 10 * HZ; /* Inactivity timeout when not on hold */ +} + +/* return action on idle timeout */ +static enum boot_mode inactivity_action(enum boot_mode cur_selection) +{ +#if defined(HAS_BUTTON_HOLD) + if(button_hold()) + return BOOT_STOP; /* power down/suspend */ + else +#endif + 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) */ +static enum boot_mode load_boot_mode(enum boot_mode mode) +{ + int fd = open("/data/rb_bl_mode.txt", O_RDONLY); + if(fd >= 0) + { + read(fd, &mode, sizeof(mode)); + close(fd); + } + return mode; +} + +static void save_boot_mode(enum boot_mode mode) +{ + int fd = open("/data/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC); + if(fd >= 0) + { + write(fd, &mode, sizeof(mode)); + close(fd); + } +} + +static 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; +#if defined(HAS_BUTTON_HOLD) + bool hold_status = button_hold(); +#endif + 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 defined(HAS_BUTTON_HOLD) + if(button_hold()) + { + lcd_set_foreground(LCD_RGBPACK(255, 0, 0)); + display_text_center(0, "ON HOLD!"); + } + else +#endif + { + 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_hibyicon : + (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) ? "HIBY PLAYER" : + (mode == BOOT_ROCKBOX) ? "ROCKBOX" : "TOOLS"; + + int desc_height; + lcd_getstringsize(desc, NULL, &desc_height); + display_text_center(LCD_HEIGHT - 3*desc_height, 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); + +#if defined(HAS_BUTTON_HOLD) + /* record action, changing HOLD counts as action */ + if(btn & BUTTON_MAIN || hold_status != button_hold()) + last_activity = current_tick; + + hold_status = button_hold(); +#else + if(btn & BUTTON_MAIN) + last_activity = current_tick; +#endif + /* 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 || btn == BUTTON_REW) + mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT; + if(btn == BUTTON_RIGHT || btn == BUTTON_UP || btn == BUTTON_FF) + 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(); +} + +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_LEFT) + { + free(buf); + return btn == BUTTON_PLAY ? choice : -1; + } + /* left/right/up/down: change mode */ + if(btn == BUTTON_UP) + choice = (choice + nr_choices - 1) % nr_choices; + if(btn == BUTTON_DOWN) + choice = (choice + 1) % nr_choices; + } +} + +void run_file(const char *name) +{ + char *dirname = "/mnt/sd_0/"; + 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("/mnt/sd_0"); + 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); +} + +static void adb(int start) +{ + pid_t pid = fork(); + if(pid == 0) + { + execlp("/etc/init.d/K90adb", "K90adb", start ? "start" : "stop", NULL); + _exit(42); + } + int status; + waitpid(pid, &status, 0); +#if 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); + } +#endif +} + +static void tools_screen(void) +{ + const char *choices[] = {"ADB start", "ADB stop", "Run script", "Restart", "Shutdown"}; + int choice = choice_screen("TOOLS MENU", true, 5, choices); + if(choice == 0) + { + /* run service menu */ + printf("Starting ADB service...\n"); + fflush(stdout); + adb(1); + } + else if(choice == 1) + { + printf("Stopping ADB service...\n"); + fflush(stdout); + adb(0); + } + else if(choice == 2) + { + run_script_menu(); + } +// else if(choice == 2) +// nwz_power_restart(); + else if(choice == 4) + power_off(); +} + +/* open log file */ +static int open_log(void) +{ + /* open regular log file */ + int fd = open("/mnt/sd_0/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("/mnt/sd_0/rockbox.log", "/mnt_sd0/rockbox.log.1"); + /* re-open the file, truncate in case the move was unsuccessful */ + return open("/mnt/sd_0/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); + } + /* print version */ + printf("Rockbox boot loader\n"); + printf("Version: %s\n", rbversion); + printf("%s\n", MODEL_NAME); + + 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); + + /* 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/bin/hiby_player", 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("/mnt/sd_0/.rockbox/rockbox.rocker", "rockbox.rocker", NULL); + printf("execvp failed: %s\n", strerror(errno)); + /* fallback to OF in case of failure */ + error_screen("Cannot boot Rockbox"); + sleep(5 * HZ); + } + else + { + 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; +} -- cgit v1.2.3