/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2007 Dave Chapman
 *
 * USB code based on ifp-line - http://ifp-driver.sourceforge.net
 *
 * ifp-line is (C) Pavel Kriz, Jun Yamishiro and Joe Roback and
 * licensed under the GPL (v2)
 *
 *
 * 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 <stdio.h>
#include <inttypes.h>
#include <usb.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_FIRMWARESIZE   (10*1024*1024)   /* Arbitrary limit (for safety) */

/* For win32 compatibility: */
#ifndef O_BINARY
#define O_BINARY 0
#endif

struct device_t
{
    char* name;
    char* label;
    uint16_t productid;
    uint32_t loadaddr;
    uint32_t sdcfg;
};

static struct device_t devices[] = 
{ 
    {"c100",     "Sansa C100 series",            0xb021, 0x20000000, 0x42e97010 },
    {"m200",     "Sansa M200 series",            0xb021, 0x20000000, 0x42e97010 },
    {"cowond2",  "Cowon D2",                     0xb011, 0x20000000, 0xa2e92010 },
    {"cowonj3",  "Cowon J3",                     0xb057, 0x20000000, 0xaaac7800 },
    {"cowons9",  "Cowon S9",                     0xb057, 0x20000000, 0xa1102800 },
    {"iaudio6",  "iAudio 6",                     0xb021, 0x20000000, 0x62e97010 },
    {"iaudio7",  "iAudio 7",                     0xb021, 0x20000000, 0x62e97010 },
    {"logikdax", "Logik DAX 1GB DAB/MP3 player", 0xb021, 0x20000000, 0x52e97410 },
    {"x20",      "iRiver X20",                   0xb051, 0x20000000, 0x02e92010 },
    {"ypp2",     "Samsung YP-P2",                0xb011, 0x20000000, 0x22e92010 },
    {"ypk3",     "Samsung YP-K3",                0xb021, 0x20000000, 0x62e92018 },
    {"ypt10",    "Samsung YP-T10",               0xb011, 0x20000000, 0x62e97080 },
};

#define NUM_DEVICES ((sizeof(devices) / sizeof(struct device_t)))

int find_device(char* devname)
{
    unsigned int i = 0;

    while ((i < NUM_DEVICES) && (strcmp(devices[i].name,devname)))
        i++;

    if (i==NUM_DEVICES)
        return -1;
    else
        return i;
}

void print_devices(void)
{
    unsigned int i;

    printf("Valid devices are:\n");
    for (i=0; i<NUM_DEVICES; i++)
    {
        printf("  %10s - %s\n",devices[i].name,devices[i].label);
    }
}

/* USB IDs for USB Boot Mode */
#define TCC_VENDORID    0x140e

#define TCC_BULK_TO       1
#define TOUT              5000
#define PACKET_SIZE       64  /* Number of bytes to send in one write */

#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif

static void put_int32le(uint32_t x, char* p)
{
    p[0] = x & 0xff;
    p[1] = (x >> 8) & 0xff;
    p[2] = (x >> 16) & 0xff;
    p[3] = (x >> 24) & 0xff;
}

int upload_app(usb_dev_handle* dh, int device, char* p, int len)
{
    char buf[PACKET_SIZE];
    int err;
    int i;

    /* Send the header - Destination address, length and SDCFG value */
    memset(buf, 0, PACKET_SIZE);

    put_int32le(0xf0000000, buf);   /* Unknown - always the same */
    put_int32le(len / PACKET_SIZE, buf + 4);
    put_int32le(devices[device].loadaddr, buf + 8);
    put_int32le(devices[device].sdcfg, buf + 12);

    err = usb_bulk_write(dh, TCC_BULK_TO, buf, PACKET_SIZE, TOUT);

    if (err < 0)
    {
        fprintf(stderr,"[ERR]  Error writing header\n");
        fprintf(stderr,"[ERR]  Bulk write error (%d, %s)\n", err, strerror(-err));
        return -1;
    }

    /* Now send the data, PACKET_SIZE bytes at a time. */

    for (i=0 ; i < (len / PACKET_SIZE) ; i++)
    {
        err = usb_bulk_write(dh, TCC_BULK_TO, p, PACKET_SIZE, TOUT);

        if (err < 0)
        {
            fprintf(stderr,"[ERR]  Error writing data\n");
            fprintf(stderr,"[ERR]  Bulk write error (%d, %s)\n", err, strerror(-err));
            return -1;
        }

        p += PACKET_SIZE;
    }

    return 0;
}


/* The main function */

int do_patching(int device, char* buf, int len)
{
    struct usb_bus *busses;
    struct usb_bus *bus;
    struct usb_device *tmp_dev;
    struct usb_device *dev = NULL;
    usb_dev_handle *dh;
    int err;

    fprintf(stderr,"[INFO] Searching for TCC device...\n");
 
    usb_init();
    if(usb_find_busses() < 0) {
        fprintf(stderr, "[ERR]  Could not find any USB busses.\n");
        return -1;
    }

    if (usb_find_devices() < 0) {
        fprintf(stderr, "[ERR]  USB devices not found(nor hubs!).\n");
        return -1;
    }

    /* C calling convention, it's not nice to use global stuff */
    busses = usb_get_busses();

    for (bus = busses; bus; bus = bus->next) {
        for (tmp_dev = bus->devices; tmp_dev; tmp_dev = tmp_dev->next) {
            //printf("Found Vendor %04x Product %04x\n",tmp_dev->descriptor.idVendor, tmp_dev->descriptor.idProduct);
            if (tmp_dev->descriptor.idVendor == TCC_VENDORID &&
                tmp_dev->descriptor.idProduct == devices[device].productid) {

                dev = tmp_dev;
                goto found;

            }
        }
    }

    if (dev == NULL) {
        fprintf(stderr, "[ERR]  TCC device not found.\n");
        fprintf(stderr, "[ERR]  Ensure your TCC device is in USB boot mode and run tcctool again.\n");
        return -1;
    }

found:
    if ( (dh = usb_open(dev)) == NULL) {
        fprintf(stderr,"[ERR]  Unable to open TCC device.\n");
        return -1;
    }

    err = usb_set_configuration(dh, 1);

    if (err < 0)  {
        fprintf(stderr, "[ERR]  usb_set_configuration failed (%d)\n", err);
        usb_close(dh);
        return -1;
    }

    /* "must be called" written in the libusb documentation */
    err = usb_claim_interface(dh, dev->config->interface->altsetting->bInterfaceNumber);
    if (err < 0) {
        fprintf(stderr, "[ERR]  Unable to claim interface (%d)\n", err);
        usb_close(dh);
        return -1;
    }

    fprintf(stderr,"[INFO] Found TCC device, uploading application.\n");

    /* Now we can transfer the application to the device. */ 

    if ( (err = upload_app(dh, device, buf, len)) < 0)
    {
        fprintf(stderr,"[ERR]  Upload of application failed.\n");
    }
    else
    {
        fprintf(stderr,"[INFO] Patching application uploaded successfully!\n");
    }

    /* release claimed interface */
    usb_release_interface(dh, dev->config->interface->altsetting->bInterfaceNumber);
    usb_close(dh);

    return err < 0 ? -1: 0;
}

off_t filesize(int fd) {
    struct stat buf;

    if (fstat(fd,&buf) < 0) {
        perror("[ERR]  Checking filesize of input file");
        return -1;
    } else {
        return(buf.st_size);
    }
}

void print_usage(void)
{
    printf("Usage: tcctool -d devicename firmware.bin\n");
}

int main(int argc, char* argv[])
{
    char* buf;
    int n,len,padded_len;
    int fd;
    int device;

    printf("tcctool " VERSION " - (C) 2007-2010 Dave Chapman\n");
    printf("This is free software; see the source for copying conditions.  There is NO\n");
    printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n");

    if (argc != 4)
    {
        print_usage();
        print_devices();
        return 1;
    }

    if (strcmp(argv[1],"-d"))
    {
        print_usage();
        print_devices();
        return 2;
    }

    device = find_device(argv[2]);

    if (device < 0)
    {
        printf("[ERR]  Unknown device \"%s\"\n",argv[2]);
        print_devices();
        return 3;
    }

    printf("[INFO] Using device \"%s\"\n",devices[device].label);
    fd = open(argv[3], O_RDONLY|O_BINARY);
    if (fd < 0)
    {
        printf("[ERR]  Could not open %s\n", argv[3]);
        return 4;
    }

    len = filesize(fd);

    if (len > MAX_FIRMWARESIZE)
    {
        printf("[ERR]  Firmware file too big\n");
        close(fd);
        return 5;
    }

    /* Round len up to multiple of PACKET_SIZE */
    padded_len = (len + PACKET_SIZE) & ~(PACKET_SIZE-1);

    buf = malloc(padded_len);
    if (buf == NULL)
    {
        printf("[ERR]  Could not allocate memory.\n");
        close(fd);
        return 6;
    }
    
    n = read(fd, buf, len);
    if (n != len)
    {
        printf("[ERR]  Short read.\n");
        close(fd);
        return 7;
    }
    close(fd);

    if (do_patching(device, buf, padded_len))
    {
        return 8;
    }

    return 0;
}
