| /*************************************************************************** |
| * __________ __ ___. |
| * 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 <stdio.h> |
| #include <errno.h> |
| #include "../kernel-internal.h" |
| #include "gcc_extensions.h" |
| #include "string.h" |
| #include "adc.h" |
| #include "powermgmt.h" |
| #include "storage.h" |
| #include "file_internal.h" |
| #include "dir.h" |
| #include "file.h" |
| #include "disk.h" |
| #include "common.h" |
| #include "rb-loader.h" |
| #include "loader_strerror.h" |
| #include "power.h" |
| #include "backlight.h" |
| #include "usb.h" |
| #include "button.h" |
| #include "font.h" |
| #include "lcd.h" |
| #include "version.h" |
| |
| /* Show the Rockbox logo - in show_logo.c */ |
| extern void show_logo(void); |
| |
| #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; |
| |
| /* Show a message + "Shutting down...", then power off the device */ |
| static void display_message_and_power_off(int timeout, const char *msg) |
| { |
| verbose = true; |
| printf(msg); |
| printf("Shutting down..."); |
| sleep(timeout); |
| power_off(); |
| } |
| |
| static void check_battery_safe(void) |
| { |
| if (battery_level_safe()) |
| return; |
| |
| display_message_and_power_off(HZ, "Battery low"); |
| } |
| |
| /* TODO: Handle charging while connected */ |
| static void handle_usb(int connect_timeout) |
| { |
| long end_tick = 0; |
| int button; |
| |
| /* We need full button and backlight handling now */ |
| backlight_init(); |
| button_init(); |
| backlight_on(); |
| |
| /* Start the USB driver */ |
| usb_init(); |
| usb_start_monitoring(); |
| |
| /* Wait for threads to connect or cable is pulled */ |
| printf("USB: Connecting"); |
| |
| if (connect_timeout != TIMEOUT_BLOCK) |
| end_tick = current_tick + connect_timeout; |
| |
| while (1) |
| { |
| button = button_get_w_tmo(HZ/2); |
| |
| if (button == SYS_USB_CONNECTED) |
| break; /* Hit */ |
| |
| if (connect_timeout != TIMEOUT_BLOCK && |
| TIME_AFTER(current_tick, end_tick)) |
| { |
| /* Timed out waiting for the connect - will happen when connected |
| * to a charger through the USB port */ |
| printf("USB: Timed out"); |
| break; |
| } |
| |
| if (usb_plugged() == USB_EXTRACTED) |
| break; /* Cable pulled */ |
| } |
| |
| if (button == SYS_USB_CONNECTED) |
| { |
| /* Switch to verbose mode if not in it so that the status updates |
| * are shown */ |
| verbose = true; |
| /* Got the message - wait for disconnect */ |
| printf("Bootloader USB mode"); |
| |
| usb_acknowledge(SYS_USB_CONNECTED_ACK); |
| |
| while (1) |
| { |
| button = button_get_w_tmo(HZ/2); |
| if (button == SYS_USB_DISCONNECTED) |
| break; |
| |
| check_battery_safe(); |
| } |
| |
| backlight_on(); |
| /* Sleep a little to let the backlight ramp up */ |
| sleep(HZ*5/4); |
| } |
| |
| /* Put drivers initialized for USB connection into a known state */ |
| usb_close(); |
| button_close(); |
| backlight_close(); |
| } |
| |
| 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 && errno != EEXIST) |
| { |
| 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* entry; |
| DIR* dir; |
| int fd; |
| int rc; |
| |
| dir = opendir(basedir); |
| |
| while ((entry = readdir(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) |
| { |
| verbose = true; |
| printf("Found rockbox binary. Moving..."); |
| close(fd); |
| 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) |
| { |
| verbose = true; |
| 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 <= EFILE_EMPTY) |
| error(EBOOTFILE, rc, true); |
| |
| /* Pause to look at messages */ |
| while (1) |
| { |
| int button = button_read_device(); |
| |
| /* Ignore settings reset */ |
| if (button == BUTTON_NONE || button == BUTTON_MENU) |
| break; |
| |
| sleep(HZ/5); |
| |
| check_battery_safe(); |
| |
| /* If the disk powers off, the firmware will lock at startup */ |
| storage_spin(); |
| } |
| |
| /* Put drivers into a known state */ |
| button_close_device(); |
| storage_close(); |
| system_prepare_fw_start(); |
| |
| commit_discard_idcache(); |
| asm volatile ("bx %0": : "r"(start_addr)); |
| |
| /* Halt */ |
| while (1) |
| core_idle(); |
| } |
| |
| void main(void) |
| { |
| int rc; |
| int batt; |
| |
| system_init(); |
| kernel_init(); |
| |
| /* Keep button_device_init early to delay calls to button_read_device */ |
| button_init_device(); |
| |
| lcd_init(); |
| font_init(); |
| show_logo(); |
| lcd_clear_display(); |
| |
| if (button_hold()) |
| display_message_and_power_off(HZ, "Hold switch on"); |
| |
| if (button_read_device() != BUTTON_NONE) |
| verbose = true; |
| |
| printf("Gigabeat S Rockbox Bootloader"); |
| printf("Version %s", rbversion); |
| |
| adc_init(); |
| batt = _battery_voltage(); |
| printf("Battery: %d.%03d V", batt / 1000, batt % 1000); |
| check_battery_safe(); |
| |
| rc = storage_init(); |
| if(rc) |
| error(EATA, rc, true); |
| |
| filesystem_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. */ |
| if (usb_plugged() == USB_INSERTED) |
| handle_usb(HZ*2); |
| |
| handle_untar(); |
| handle_firmware_load(); /* No return */ |
| } |