/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006 by Greg White * * 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 "config.h" #include "system.h" #include #include "kernel.h" #include "gcc_extensions.h" #include "string.h" #include "adc.h" #include "powermgmt.h" #include "storage.h" #include "dir.h" #include "disk.h" #include "common.h" #include "backlight.h" #include "usb.h" #include "button.h" #include "font.h" #include "lcd.h" #include "usb-target.h" #include "version.h" #define TAR_CHUNK 512 #define TAR_HEADER_SIZE 157 /* Where files sent via MTP are stored */ static const char basedir[] = "/Content/0b00/00/"; /* Can use memory after vector table up to 0x01f00000 */ static char * const tarbuf = (char *)0x00000040; static const size_t tarbuf_size = 0x01f00000 - 0x00000040; /* Firmware data */ static void * const load_buf = 0x00000000; static const size_t load_buf_size = 0x20000000 - 0x100000; static const void * const start_addr = 0x00000000; static void show_splash(int timeout, const char *msg) { backlight_on(); reset_screen(); lcd_putsxy( (LCD_WIDTH - (SYSFONT_WIDTH * strlen(msg))) / 2, (LCD_HEIGHT - SYSFONT_HEIGHT) / 2, msg); lcd_update(); sleep(timeout); } static bool pause_if_button_pressed(bool pre_usb) { while (1) { int button = button_read_device(); if (pre_usb && !usb_plugged()) return false; /* Exit if no button or only select buttons that have other * functions */ switch (button) { case USB_BL_INSTALL_MODE_BTN: if (!pre_usb) break; /* Only before USB detect */ case BUTTON_MENU: /* Settings reset */ case BUTTON_NONE: /* Nothing pressed */ return true; } sleep(HZ/5); /* If the disk powers off, the firmware will lock at startup */ storage_spin(); } } /* TODO: Handle charging while connected */ static void handle_usb(void) { int button; /* Check if plugged and pause to look at messages. If the cable was pulled * while waiting, proceed as if it never was plugged. */ if (!usb_plugged() || !pause_if_button_pressed(true)) return; /** Enter USB mode **/ /* We need full button and backlight handling now */ backlight_init(); button_init(); /* Start the USB driver */ usb_init(); usb_start_monitoring(); /* Wait for threads to connect or cable is pulled */ show_splash(HZ/2, "Waiting for USB"); while (1) { button = button_get_w_tmo(HZ/2); if (button == SYS_USB_CONNECTED) break; /* Hit */ if (!usb_plugged()) break; /* Cable pulled */ } if (button == SYS_USB_CONNECTED) { /* Got the message - wait for disconnect */ show_splash(0, "Bootloader USB mode"); usb_acknowledge(SYS_USB_CONNECTED_ACK); while (1) { button = button_get(true); if (button == SYS_USB_DISCONNECTED) { usb_acknowledge(SYS_USB_DISCONNECTED_ACK); break; } } } /* Put drivers initialized for USB connection into a known state */ backlight_on(); usb_close(); button_close(); backlight_close(); /* Sleep a little to let the backlight ramp up */ sleep(HZ*5/4); reset_screen(); } static void untar(int tar_fd) { char header[TAR_HEADER_SIZE]; char *ptr; char path[102]; int fd, i; int ret; size_t size = filesize(tar_fd); if (size > tarbuf_size) { printf("tar file too large"); /* Paranoid but proper */ return; } ret = read(tar_fd, tarbuf, filesize(tar_fd)); if (ret < 0) { printf("couldn't read tar file (%d)", ret); return; } ptr = tarbuf; while (1) { memcpy(header, ptr, TAR_HEADER_SIZE); if (*header == '\0') /* Check for EOF */ break; /* Parse the size field */ size = 0; for (i = 124 ; i < 124 + 11 ; i++) { size = (8 * size) + header[i] - '0'; } /* Skip rest of header */ ptr += TAR_CHUNK; /* Make the path absolute */ strcpy(path, "/"); strcat(path, header); if (header[156] == '0') /* file */ { int wc; fd = creat(path, 0666); if (fd < 0) { printf("failed to create file (%d)", fd); } else { wc = write(fd, ptr, size); if (wc < 0) { printf("write failed (%d)", wc); break; } close(fd); } ptr += (size + TAR_CHUNK-1) & (~(TAR_CHUNK-1)); } else if (header[156] == '5') /* directory */ { int ret; /* Remove the trailing slash */ if (path[strlen(path) - 1] == '/') path[strlen(path) - 1] = '\0'; /* Create the dir */ ret = mkdir(path); if (ret < 0 && ret != -4) { printf("failed to create dir (%d)", ret); } } } } /* Look for a tar file or rockbox binary in the MTP directory */ static void handle_untar(void) { char buf[MAX_PATH]; char tarstring[6]; char model[5]; struct dirent_uncached* entry; DIR_UNCACHED* dir; int fd; int rc; dir = opendir_uncached(basedir); while ((entry = readdir_uncached(dir))) { if (*entry->d_name == '.') continue; snprintf(buf, sizeof(buf), "%s%s", basedir, entry->d_name); fd = open(buf, O_RDONLY); if (fd < 0) continue; /* Check whether the file is a rockbox binary. */ lseek(fd, 4, SEEK_SET); rc = read(fd, model, 4); if (rc == 4) { model[4] = 0; if (strcmp(model, "gigs") == 0) { printf("Found rockbox binary. Moving..."); close(fd); remove( BOOTDIR "/" BOOTFILE); int ret = rename(buf, BOOTDIR "/" BOOTFILE); printf("returned %d", ret); sleep(HZ); break; } } /* Check whether the file is a tar file. */ lseek(fd, 257, SEEK_SET); rc = read(fd, tarstring, 5); if (rc == 5) { tarstring[5] = 0; if (strcmp(tarstring, "ustar") == 0) { printf("Found tar file. Unarchiving..."); lseek(fd, 0, SEEK_SET); untar(fd); close(fd); printf("Removing tar file"); remove(buf); break; } } close(fd); } } /* Try to load the firmware and run it */ static void NORETURN_ATTR handle_firmware_load(void) { int rc = load_firmware(load_buf, BOOTFILE, load_buf_size); if(rc < 0) error(EBOOTFILE, rc, true); /* Pause to look at messages */ pause_if_button_pressed(false); /* Put drivers into a known state */ button_close_device(); storage_close(); system_prepare_fw_start(); if (rc == EOK) { cpucache_invalidate(); asm volatile ("bx %0": : "r"(start_addr)); } /* Halt */ while (1) core_idle(); } static void check_battery(void) { int batt = battery_adc_voltage(); printf("Battery: %d.%03d V", batt / 1000, batt % 1000); /* TODO: warn on low battery or shut down */ } void main(void) { int rc; /* Flush and invalidate all caches (because vectors were written) */ cpucache_invalidate(); system_init(); kernel_init(); enable_interrupt(IRQ_FIQ_STATUS); lcd_init_device(); lcd_clear_display(); printf("Gigabeat S Rockbox Bootloader"); printf("Version " RBVERSION); /* Initialize KPP so we can poll the button states */ button_init_device(); adc_init(); check_battery(); rc = storage_init(); if(rc) { reset_screen(); error(EATA, rc, true); } disk_init(); rc = disk_mount_all(); if (rc<=0) { error(EDISK, rc, true); } printf("Init complete"); /* Do USB first since a tar or binary could be added to the MTP directory * at the time and we can untar or move after unplugging. */ handle_usb(); handle_untar(); handle_firmware_load(); /* No return */ }