/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/mount.h>
#include <sys/types.h>
#include <sys/reboot.h>
#include <sys/stat.h>

#define error(s, a...)                          \
    {                                           \
        printf("error: " s "\n", ##a);          \
        exit(-1);                               \
    }

#define error_errno(s, a...) error(s ": %s", ##a, strerror(errno))

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

enum omap_type_enum {
    OMAP4460_EMU = 0,
    OMAP4460_HS,
    OMAP4460_HS_PROD,
    OMAP4430_HS,
};

struct omap_type {
    const char *family;
    const char *type;
    unsigned long msv_val;
    const char *msv_type;
    off_t offset;
} omap_type_list[] = {
    [OMAP4460_EMU]     = {"OMAP4460", "EMU", 0x00000000, "eng",  0x1000},
    [OMAP4460_HS]      = {"OMAP4460", "HS",  0x00000000, "eng",  0x21000},
    [OMAP4460_HS_PROD] = {"OMAP4460", "HS",  0xf0000f00, "prod", 0x41000},
    [OMAP4430_HS]      = {"OMAP4430", "HS",  0x00000000, "eng",  0x61000},
};

#define IMG_PIT_OFFSET 0UL
#define IMG_SBL_OFFSET 0x81000UL

#define MMC_PIT_OFFSET 0x4400UL
#define MMC_XLOADER_OFFSET 0x20000UL
#define MMC_SBL_OFFSET 0x80000UL

#define PIT_SIZE 0x1000UL
#define XLOADER_SIZE 0x20000UL

static void drop_caches(void)
{
    int fd;
    int ret;
    char buf[] = "3\n";

    fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
    if (fd < 0)
        error_errno("failed to open /proc/sys/vm/drop_caches");

    ret = write(fd, buf, sizeof(buf));
    if (ret < 0)
        error_errno("failed to write to /proc/sys/vm/drop_caches");
}

static void read_file(const char *filename, char *buf, size_t size)
{
    int fd;
    ssize_t ret;

    fd = open(filename, O_RDONLY);
    if (fd < 0)
        error_errno("failed to open %s", filename);

    ret = read(fd, buf, size - 1);
    if (ret < 0)
        error_errno("failed to read %s", filename);
    buf[ret] = 0;
    while (buf[ret - 1] == '\n')
        buf[--ret] = 0;

    close(fd);
}

static const struct omap_type *get_omap_type(void)
{
    int fd;
    char family[10];
    char type[5];
    char msv[9];
    unsigned long msv_val;
    ssize_t ret;
    unsigned int i;

    read_file("/sys/board_properties/soc/type", type, sizeof(type));
    read_file("/sys/board_properties/soc/family", family, sizeof(family));
    read_file("/sys/board_properties/soc/msv", msv, sizeof(msv));

    msv_val = strtoul(msv, NULL, 16);

    for (i = 0; i < ARRAY_SIZE(omap_type_list); i++)
        if ((strcmp(omap_type_list[i].family, family) == 0) &&
            (strcmp(omap_type_list[i].type, type) == 0) &&
            msv_val == omap_type_list[i].msv_val)
            return &omap_type_list[i];

    error("unknown omap type %s %s %s (0x%08lx)", family, type, msv, msv_val);
}

static void zero_data(int to_fd, off_t to_offset, ssize_t size)
{
    char buf[4096];
    int ret;
    unsigned int to_write;

    memset(buf, 0, sizeof(buf));

    ret = lseek(to_fd, to_offset, SEEK_SET);
    if (ret < 0)
        error_errno("failed to seek output file to %lx", to_offset);

    while (size != 0) {
        to_write = size;
        if (to_write > sizeof(buf))
            to_write = sizeof(buf);

        ret = write(to_fd, buf, to_write);
        if (ret < 0)
            error_errno("failed to write to output file");
        size -= ret;
    }
}

static void verify_data(int to_fd, off_t to_offset,
                      int from_fd, off_t from_offset,
                      ssize_t size)
{
    char buf_to[4096];
    char buf_from[4096];
    int ret;
    int to_read;
    int c;
    char *ptr;

    ret = lseek(to_fd, to_offset, SEEK_SET);
    if (ret < 0)
        error_errno("failed to seek output file to %lx", to_offset);

    ret = lseek(from_fd, from_offset, SEEK_SET);
    if (ret < 0)
        error_errno("failed to seek input file to %lx", from_offset);

    while (size != 0) {
        to_read = sizeof(buf_to);
        if (size > 0 && to_read > size)
            to_read = size;

        ptr = buf_to;
        c = to_read;
        while (c > 0) {
            ret = read(to_fd, ptr, c);
            if (ret < 0)
                error_errno("failed to read from output file");
            if (ret == 0 && size < 0)
                return;
            if (ret == 0)
                error_errno("eof while reading output file");
            ptr += ret;
            c -= ret;
        }

        ptr = buf_from;
        c = to_read;
        while (c > 0) {
            ret = read(from_fd, ptr, c);
            if (ret < 0)
                error_errno("failed to read from input file");
            if (ret == 0 && size < 0)
                return;
            if (ret == 0)
                error_errno("eof while reading input file");
            ptr += ret;
            c -= ret;
        }

        if (memcmp(buf_from, buf_to, to_read) != 0)
            error("mismatch while verifying written data");

        size -= to_read;
    }
}

static void copy_data(int to_fd, off_t to_offset,
                      int from_fd, off_t from_offset,
                      ssize_t size)
{
    char buf[4096];
    int ret;
    int to_write;
    const char *ptr;

    ret = lseek(to_fd, to_offset, SEEK_SET);
    if (ret < 0)
        error_errno("failed to seek output file to %lx", to_offset);

    ret = lseek(from_fd, from_offset, SEEK_SET);
    if (ret < 0)
        error_errno("failed to seek input file to %lx", from_offset);

    while (size != 0) {
        ret = read(from_fd, buf, sizeof(buf));
        if (ret < 0)
            error_errno("failed to read from input file");
        if (ret == 0 && size > 0)
            error_errno("eof while reading input file");
        if (ret == 0)
            return;

        to_write = ret;
        ptr = buf;

        if (size > 0)
            size -= to_write;

        while (to_write > 0) {
            ret = write(to_fd, ptr, to_write);
            if (ret < 0)
                error_errno("failed to write to output file");
            to_write -= ret;
            ptr += ret;
        }
    }
}

static void init(void)
{
    int ret;

    umask(0);

    ret = mkdir("/dev", 0755);
    if (ret && errno != EEXIST)
        error_errno("failed to create /dev");

    ret = mkdir("/proc", 0755);
    if (ret && errno != EEXIST)
        error_errno("failed to create /proc");

    ret = mkdir("/sys", 0755);
    if (ret && errno != EEXIST)
        error_errno("failed to create /sys");

    ret = mount("proc", "/proc", "proc", 0, NULL);
    if (ret)
        error_errno("failed to mount proc");

    ret = mount("sysfs", "/sys", "sysfs", 0, NULL);
    if (ret)
        error_errno("failed to mount sys");

    ret = mkdir("/dev/block", 0755);
    if (ret && errno != EEXIST)
        error_errno("failed to create /dev/block");

    ret = mknod("/dev/block/mmcblk0", S_IFBLK | 0755, makedev(179, 0));
    if (ret)
        error_errno("failed to create mmcblk0");
}

int main(int argc, char **argv)
{
    int in_fd;
    int out_fd;
    const struct omap_type *type;

    if (getpid() == 1)
        init();

    in_fd = open("bootloader.img", O_RDONLY);
    if (in_fd < 0)
        error_errno("failed to open bootloader.img");

    out_fd = open("/dev/block/mmcblk0", O_RDWR);
    if (out_fd < 0)
        error_errno("failed to open mmcblk0");

    type = get_omap_type();

    printf("Found %s %s %s\n", type->family, type->type, type->msv_type);

    printf("Zeroing to end of sbl\n");
    zero_data(out_fd, 0, MMC_SBL_OFFSET);

    /* Don't write the partition table, let the bootloader do it on next boot */
#if 0
    printf("Writing partition-table from %lx to %lx\n",
           IMG_PIT_OFFSET, MMC_PIT_OFFSET);
    copy_data(out_fd, MMC_PIT_OFFSET, in_fd, IMG_PIT_OFFSET, PIT_SIZE);
#endif

    printf("Writing xloader from %lx to %lx\n",
           type->offset, MMC_XLOADER_OFFSET);
    copy_data(out_fd, MMC_XLOADER_OFFSET, in_fd, type->offset, XLOADER_SIZE);

    printf("Writing sbl from %lx to %lx\n",
           IMG_SBL_OFFSET, MMC_SBL_OFFSET);
    copy_data(out_fd, MMC_SBL_OFFSET, in_fd, IMG_SBL_OFFSET, -1);

#if 0
    printf("Verifying partition table\n");
    verify_data(out_fd, MMC_PIT_OFFSET, in_fd, IMG_PIT_OFFSET, PIT_SIZE);
#endif

    printf("Verifying xloader\n");
    verify_data(out_fd, MMC_XLOADER_OFFSET, in_fd, type->offset, XLOADER_SIZE);

    printf("Verifying sbl\n");
    verify_data(out_fd, MMC_SBL_OFFSET, in_fd, IMG_SBL_OFFSET, -1);

    printf("Syncing\n");
    sync();

    printf("Dropping caches\n");
    drop_caches();

    printf("Verifying xloader.img\n");
    verify_data(out_fd, MMC_XLOADER_OFFSET, in_fd, type->offset, XLOADER_SIZE);

    printf("Verifying sbl.img\n");
    verify_data(out_fd, MMC_SBL_OFFSET, in_fd, IMG_SBL_OFFSET, -1);

    printf("Done\n");

    if (getpid() == 1) {
        __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                 LINUX_REBOOT_CMD_RESTART2, "bootloader");

        while (1) { sleep(1); }
    }

    return 0;
}