blob: 9b524d0eb984871045f4a48d494464a572f74117 [file] [log] [blame]
Amaury Pouly1d121e82017-02-23 11:33:19 +01001/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2016 by Amaury Pouly
11 *
12 * Based on Rockbox iriver bootloader by Linus Nielsen Feltzing
13 * and the ipodlinux bootloader by Daniel Palffy and Bernard Leach
14 *
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation; either version 2
18 * of the License, or (at your option) any later version.
19 *
20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 * KIND, either express or implied.
22 *
23 ****************************************************************************/
24
25#include "system.h"
26#include "lcd.h"
27#include "backlight.h"
28#include "button-target.h"
29#include "button.h"
30#include "../kernel/kernel-internal.h"
31#include "core_alloc.h"
32#include "filesystem-app.h"
33#include "nvp-nwz.h"
34#include "power-nwz.h"
35#include "lcd.h"
36#include "font.h"
37#include "power.h"
38#include <string.h>
39#include <stdlib.h>
40#include <unistd.h>
41#include <sys/types.h>
42#include <sys/stat.h>
43#include <fcntl.h>
44#include <dirent.h>
45#include <sys/wait.h>
46#include <stdarg.h>
Amaury Poulyaedf4d22017-09-17 22:49:09 +020047#include "version.h"
Amaury Pouly1d121e82017-02-23 11:33:19 +010048
49/* all images must have the following size */
50#define ICON_WIDTH 130
51#define ICON_HEIGHT 130
52
53/* images */
54#include "bitmaps/rockboxicon.h"
55#include "bitmaps/toolsicon.h"
56
57/* don't issue an error when parsing the file for dependencies */
58#if defined(BMPWIDTH_rockboxicon) && (BMPWIDTH_rockboxicon != ICON_WIDTH || \
59 BMPHEIGHT_rockboxicon != ICON_HEIGHT)
60#error rockboxicon has the wrong resolution
61#endif
62#if defined(BMPWIDTH_toolsicon) && (BMPWIDTH_toolsicon != ICON_WIDTH || \
63 BMPHEIGHT_toolsicon != ICON_HEIGHT)
64#error toolsicon has the wrong resolution
65#endif
66
Amaury Poulyac57f962017-09-10 21:55:35 +020067/* the A860 does not have left/right/up/down but it has rew/ff so pretend we
68 * always have rew/ff */
69#ifndef BUTTON_REW
70#define BUTTON_REW BUTTON_LEFT
71#endif
72#ifndef BUTTON_FF
73#define BUTTON_FF BUTTON_RIGHT
74#endif
75
Amaury Pouly1d121e82017-02-23 11:33:19 +010076/* buffer for Sony image, filled from NVP */
77unsigned short sonyicon[ICON_WIDTH * ICON_HEIGHT];
78const struct bitmap bm_sonyicon =
79{
80 .width = ICON_WIDTH,
81 .height = ICON_HEIGHT,
82 .format = FORMAT_NATIVE,
83 .data = (unsigned char*)sonyicon
84};
85
86/* return icon y position (x is always centered) */
87int get_icon_y(void)
88{
89 /* adjust so that this contains the Sony logo and produces a nice logo
90 * when used with rockbox */
91 if(LCD_HEIGHT == 320)
92 return 70;
93 else if(LCD_HEIGHT == 400)
94 return 100;
95 else
96 return LCD_HEIGHT / 2 - ICON_HEIGHT + 30; /* guess, probably won't work */
97}
98
99/* Sony logo extraction */
100bool extract_sony_logo(void)
101{
102 /* load the entire image from the nvp */
103 int bti_size = nwz_nvp_read(NWZ_NVP_BTI, NULL);
104 if(bti_size < 0)
105 return false;
106 unsigned short *bti = malloc(bti_size);
107 if(nwz_nvp_read(NWZ_NVP_BTI, bti) != bti_size)
108 return false;
109 /* compute the offset in the image of the logo itself */
110 int x_off = (LCD_WIDTH - ICON_WIDTH) / 2; /* logo is centered horizontally */
111 int y_off = get_icon_y();
112 /* extract part of the image */
113 for(int y = 0; y < ICON_HEIGHT; y++)
114 {
115 memcpy(sonyicon + ICON_WIDTH * y,
116 bti + LCD_WIDTH * (y + y_off) + x_off, ICON_WIDTH * sizeof(unsigned short));
117 }
118 free(bti);
119 return true;
120}
121
122/* Important Note: this bootloader is carefully written so that in case of
123 * error, the OF is run. This seems like the safest option since the OF is
124 * always there and might do magic things. */
125
126enum boot_mode
127{
128 BOOT_ROCKBOX,
129 BOOT_TOOLS,
130 BOOT_OF,
131 BOOT_COUNT,
132 BOOT_USB, /* special */
133 BOOT_STOP, /* power down/suspend */
134};
135
136static void display_text_center(int y, const char *text)
137{
138 int width;
139 lcd_getstringsize(text, &width, NULL);
140 lcd_putsxy(LCD_WIDTH / 2 - width / 2, y, text);
141}
142
143static void display_text_centerf(int y, const char *format, ...)
144{
145 char buf[1024];
146 va_list ap;
147 va_start(ap, format);
148
149 vsnprintf(buf, sizeof(buf), format, ap);
150 display_text_center(y, buf);
151}
152
153/* get timeout before taking action if the user doesn't touch the device */
154int get_inactivity_tmo(void)
155{
156 if(button_hold())
157 return 5 * HZ; /* Inactivity timeout when on hold */
158 else
159 return 10 * HZ; /* Inactivity timeout when not on hold */
160}
161
162/* return action on idle timeout */
163enum boot_mode inactivity_action(enum boot_mode cur_selection)
164{
165 if(button_hold())
166 return BOOT_STOP; /* power down/suspend */
167 else
168 return cur_selection; /* return last choice */
169}
170
171/* we store the boot mode in a file in /tmp so we can reload it between 'boots'
172 * (since the mostly suspends instead of powering down) */
173enum boot_mode load_boot_mode(enum boot_mode mode)
174{
175 int fd = open("/tmp/rb_bl_mode.txt", O_RDONLY);
176 if(fd >= 0)
177 {
178 read(fd, &mode, sizeof(mode));
179 close(fd);
180 }
181 return mode;
182}
183
184void save_boot_mode(enum boot_mode mode)
185{
186 int fd = open("/tmp/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC);
187 if(fd >= 0)
188 {
189 write(fd, &mode, sizeof(mode));
190 close(fd);
191 }
192}
193
194enum boot_mode get_boot_mode(void)
195{
196 /* load previous mode, or start with rockbox if none */
197 enum boot_mode init_mode = load_boot_mode(BOOT_ROCKBOX);
198 /* wait for user action */
199 enum boot_mode mode = init_mode;
200 int last_activity = current_tick;
201 bool hold_status = button_hold();
202 while(true)
203 {
204 /* on usb detect, return to usb
205 * FIXME this is a hack, we need proper usb detection */
206 if(power_input_status() & POWER_INPUT_USB_CHARGER)
207 {
208 /* save last choice */
209 save_boot_mode(mode);
210 return BOOT_USB;
211 }
212 /* inactivity detection */
213 int timeout = last_activity + get_inactivity_tmo();
214 if(TIME_AFTER(current_tick, timeout))
215 {
216 /* save last choice */
217 save_boot_mode(mode);
218 return inactivity_action(mode);
219 }
220 /* redraw */
221 lcd_clear_display();
222 /* display top text */
223 if(button_hold())
224 {
225 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
226 display_text_center(0, "ON HOLD!");
227 }
228 else
229 {
230 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
231 display_text_center(0, "SELECT PLAYER");
232 }
233 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
234 /* display icon */
235 const struct bitmap *icon = (mode == BOOT_OF) ? &bm_sonyicon :
236 (mode == BOOT_ROCKBOX) ? &bm_rockboxicon : &bm_toolsicon;
237 lcd_bmp(icon, (LCD_WIDTH - ICON_WIDTH) / 2, get_icon_y());
238 /* display bottom description */
239 const char *desc = (mode == BOOT_OF) ? "SONY" :
240 (mode == BOOT_ROCKBOX) ? "ROCKBOX" : "TOOLS";
241 display_text_center(get_icon_y() + ICON_HEIGHT + 30, desc);
242 /* display arrows */
243 int arrow_width, arrow_height;
244 lcd_getstringsize("<", &arrow_width, &arrow_height);
245 int arrow_y = get_icon_y() + ICON_HEIGHT / 2 - arrow_height / 2;
246 lcd_putsxy(arrow_width / 2, arrow_y, "<");
247 lcd_putsxy(LCD_WIDTH - 3 * arrow_width / 2, arrow_y, ">");
248
249 lcd_set_foreground(LCD_RGBPACK(0, 255, 0));
250 display_text_centerf(LCD_HEIGHT - arrow_height * 3 / 2, "timeout in %d sec",
251 (timeout - current_tick + HZ - 1) / HZ);
252
253 lcd_update();
254
255 /* wait for a key */
256 int btn = button_get_w_tmo(HZ / 10);
257 /* record action, changing HOLD counts as action */
258 if(btn & BUTTON_MAIN || hold_status != button_hold())
259 last_activity = current_tick;
260 hold_status = button_hold();
261 /* ignore release, allow repeat */
262 if(btn & BUTTON_REL)
263 continue;
264 if(btn & BUTTON_REPEAT)
265 btn &= ~BUTTON_REPEAT;
266 /* play -> stop loop and return mode */
267 if(btn == BUTTON_PLAY)
268 break;
269 /* left/right/up/down: change mode */
Amaury Poulyac57f962017-09-10 21:55:35 +0200270 if(btn == BUTTON_LEFT || btn == BUTTON_DOWN || btn == BUTTON_REW)
Amaury Pouly1d121e82017-02-23 11:33:19 +0100271 mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT;
Amaury Poulyac57f962017-09-10 21:55:35 +0200272 if(btn == BUTTON_RIGHT || btn == BUTTON_UP || btn == BUTTON_FF)
Amaury Pouly1d121e82017-02-23 11:33:19 +0100273 mode = (mode + 1) % BOOT_COUNT;
274 }
275
276 /* save mode */
277 save_boot_mode(mode);
278 return mode;
279}
280
281void error_screen(const char *msg)
282{
283 lcd_clear_display();
284 lcd_putsf(0, 0, msg);
285 lcd_update();
286}
287
288void create_sony_logo(void)
289{
290 for(int y = 0; y < ICON_HEIGHT; y++)
291 for(int x = 0; x < ICON_WIDTH; x++)
292 sonyicon[y * ICON_WIDTH + x] = 0xf81f;
293}
294
295int choice_screen(const char *title, bool center, int nr_choices, const char *choices[])
296{
297 int choice = 0;
298 int max_len = 0;
299 int h;
300 lcd_getstringsize("x", NULL, &h);
301 for(int i = 0; i < nr_choices; i++)
302 {
303 int len = strlen(choices[i]);
304 if(len > max_len)
305 max_len = len;
306 }
307 char *buf = malloc(max_len + 10);
308 int top_y = 2 * h;
309 int nr_lines = (LCD_HEIGHT - top_y) / h;
310 while(true)
311 {
312 /* make sure choice is visible */
313 int offset = choice - nr_lines / 2;
314 if(offset < 0)
315 offset = 0;
316 lcd_clear_display();
317 /* display top text */
318 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
319 display_text_center(0, title);
320 int line = 0;
321 for(int i = 0; i < nr_choices && line < nr_lines; i++)
322 {
323 if(i < offset)
324 continue;
325 if(i == choice)
326 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
327 else
328 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
329 sprintf(buf, "%s", choices[i]);
330 if(center)
331 display_text_center(top_y + h * line, buf);
332 else
333 lcd_putsxy(0, top_y + h * line, buf);
334 line++;
335 }
336
337 lcd_update();
338
339 /* wait for a key */
340 int btn = button_get_w_tmo(HZ / 10);
341 /* ignore release, allow repeat */
342 if(btn & BUTTON_REL)
343 continue;
344 if(btn & BUTTON_REPEAT)
345 btn &= ~BUTTON_REPEAT;
346 /* play -> stop loop and return mode */
347 if(btn == BUTTON_PLAY || btn == BUTTON_BACK)
348 {
349 free(buf);
350 return btn == BUTTON_PLAY ? choice : -1;
351 }
352 /* left/right/up/down: change mode */
Amaury Poulyac57f962017-09-10 21:55:35 +0200353 if(btn == BUTTON_LEFT || btn == BUTTON_UP || btn == BUTTON_REW)
Amaury Pouly1d121e82017-02-23 11:33:19 +0100354 choice = (choice + nr_choices - 1) % nr_choices;
Amaury Poulyac57f962017-09-10 21:55:35 +0200355 if(btn == BUTTON_RIGHT || btn == BUTTON_DOWN || btn == BUTTON_FF)
Amaury Pouly1d121e82017-02-23 11:33:19 +0100356 choice = (choice + 1) % nr_choices;
357 }
358}
359
360void run_file(const char *name)
361{
362 char *dirname = "/contents/";
363 char *buf = malloc(strlen(dirname) + strlen(name) + 1);
364 sprintf(buf, "%s%s", dirname, name);
365
366 lcd_clear_display();
367 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
368 lcd_putsf(0, 0, "Running %s", name);
369 lcd_update();
370
371 pid_t pid = fork();
372 if(pid == 0)
373 {
374 execlp("sh", "sh", buf, NULL);
375 _exit(42);
376 }
377 int status;
378 waitpid(pid, &status, 0);
379 if(WIFEXITED(status))
380 {
381 lcd_set_foreground(LCD_RGBPACK(255, 201, 0));
382 lcd_putsf(0, 1, "program returned %d", WEXITSTATUS(status));
383 }
384 else
385 {
386 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
387 lcd_putsf(0, 1, "an error occured: %x", status);
388 }
389 lcd_set_foreground(LCD_RGBPACK(255, 0, 0));
390 lcd_putsf(0, 3, "Press any key or wait");
391 lcd_update();
392 /* wait a small time */
393 sleep(HZ);
394 /* ignore event */
395 while(button_get(false) != 0) {}
396 /* wait for any key or timeout */
397 button_get_w_tmo(4 * HZ);
398}
399
400void run_script_menu(void)
401{
402 const char **entries = NULL;
403 int nr_entries = 0;
404 DIR *dir = opendir("/contents/");
405 struct dirent *ent;
406 while((ent = readdir(dir)))
407 {
408 if(ent->d_type != DT_REG)
409 continue;
410 entries = realloc(entries, (nr_entries + 1) * sizeof(const char *));
411 entries[nr_entries++] = strdup(ent->d_name);
412 }
413 closedir(dir);
414 int idx = choice_screen("RUN SCRIPT", false, nr_entries, entries);
415 if(idx >= 0)
416 run_file(entries[idx]);
417 for(int i = 0; i < nr_entries; i++)
418 free((char *)entries[i]);
419 free(entries);
420}
421
422void tools_screen(void)
423{
424 const char *choices[] = {"Service menu", "Run script", "Restart", "Shutdown"};
425 int choice = choice_screen("TOOLS MENU", true, 4, choices);
426 if(choice == 0)
427 {
428 /* run service menu */
429 fflush(stdout);
430 execl("/usr/local/bin/mptapp", "mptapp", NULL);
431 error_screen("Cannot boot service menu");
432 sleep(5 * HZ);
433 }
434 else if(choice == 1)
435 run_script_menu();
436 else if(choice == 2)
437 nwz_power_restart();
438 else if(choice == 3)
439 nwz_power_shutdown();
440}
441
442/* open log file */
443int open_log(void)
444{
445 /* open regular log file */
446 int fd = open("/contents/rockbox.log", O_RDWR | O_CREAT | O_APPEND);
447 /* get its size */
448 struct stat stat;
449 if(fstat(fd, &stat) != 0)
450 return fd; /* on error, don't do anything */
451 /* if file is too large, rename it and start a new log file */
452 if(stat.st_size < 1000000)
453 return fd;
454 close(fd);
455 /* move file */
456 rename("/contents/rockbox.log", "/contents/rockbox.log.1");
457 /* re-open the file, truncate in case the move was unsuccessful */
458 return open("/contents/rockbox.log", O_RDWR | O_CREAT | O_APPEND | O_TRUNC);
459}
460
461int main(int argc, char **argv)
462{
463 (void) argc;
464 (void) argv;
465 /* redirect stdout and stderr to have error messages logged somewhere on the
466 * user partition */
467 int fd = open_log();
468 if(fd >= 0)
469 {
470 dup2(fd, fileno(stdout));
471 dup2(fd, fileno(stderr));
472 close(fd);
473 }
Amaury Poulyaedf4d22017-09-17 22:49:09 +0200474 /* print version */
475 printf("Rockbox boot loader\n");
476 printf("Version: %s\n", rbversion);
477 printf("%s\n", MODEL_NAME);
478
Amaury Pouly1d121e82017-02-23 11:33:19 +0100479 system_init();
480 core_allocator_init();
481 kernel_init();
482 paths_init();
483 lcd_init();
484 font_init();
485 button_init();
486 backlight_init();
487 backlight_set_brightness(DEFAULT_BRIGHTNESS_SETTING);
488 /* try to load the extra font we install on the device */
489 int font_id = font_load("/usr/local/share/rockbox/bootloader.fnt");
490 if(font_id >= 0)
491 lcd_setfont(font_id);
492 /* extract logo */
493 if(!extract_sony_logo())
494 create_sony_logo();
495 /* run all tools menu */
496 while(true)
497 {
498 enum boot_mode mode = get_boot_mode();
499 if(mode == BOOT_USB || mode == BOOT_OF)
500 {
501 fflush(stdout);
502 fflush(stderr);
503 close(fileno(stdout));
504 close(fileno(stderr));
505 /* for now the only way we have to trigger USB mode it to run the OF */
506 /* boot OF */
507 execvp("/usr/local/bin/SpiderApp.of", argv);
508 error_screen("Cannot boot OF");
509 sleep(5 * HZ);
510 }
511 else if(mode == BOOT_TOOLS)
512 {
513 tools_screen();
514 }
515 else if(mode == BOOT_ROCKBOX)
516 {
517 /* Rockbox expects /.rockbox to contain themes, rocks, etc, but we
518 * cannot easily create this symlink because the root filesystem is
519 * mounted read-only. Although we could remount it read-write temporarily,
520 * this is neededlessly complicated and we defer this job to the dualboot
521 * install script */
522 fflush(stdout);
523 execl("/contents/.rockbox/rockbox.sony", "rockbox.sony", NULL);
524 printf("execvp failed: %s\n", strerror(errno));
525 /* fallback to OF in case of failure */
526 error_screen("Cannot boot Rockbox");
527 sleep(5 * HZ);
528 }
529 else
530 {
Amaury Pouly1d121e82017-02-23 11:33:19 +0100531 printf("suspend\n");
532 nwz_power_suspend();
533 }
534 }
535 /* if we reach this point, everything failed, so return an error so that
536 * sysmgrd knows something is wrong */
537 return 1;
538}