#include "toboot-api.h"
#include "toboot-internal.h"

#define XXH_NO_LONG_LONG
#define XXH_FORCE_ALIGN_CHECK 0
#define XXH_FORCE_NATIVE_FORMAT 0
#define XXH_PRIVATE_API
#include "xxhash.h"

static const struct toboot_configuration *current_config = NULL;

uint32_t tb_first_free_address(void) {
    extern uint32_t _eflash;
    extern uint32_t _sdtext;
    extern uint32_t _edtext;
#define PADDR(x) ((uint32_t)&x)
#define PAGE_SIZE 1024
#define PAGE_ROUND_UP(x) ( (((uint32_t)(x)) + PAGE_SIZE-1) & (~(PAGE_SIZE-1)) ) 
    return PAGE_ROUND_UP(PADDR(_eflash) + (PADDR(_edtext) - PADDR(_sdtext)));
#undef PADDR
#undef PAGE_SIZE
#undef PAGE_ROUND_UP
}

uint32_t tb_config_hash(const struct toboot_configuration *cfg) {
    return XXH32(cfg, sizeof(*cfg) - 4, TOBOOT_HASH_SEED);
}

void tb_sign_config(struct toboot_configuration *cfg) {
    cfg->reserved_hash = tb_config_hash(cfg);
}

int tb_valid_signature_at_page(uint32_t page) {
    const struct toboot_configuration *cfg = (const struct toboot_configuration *)((page * 1024) + 0x94);
    if (cfg->magic != TOBOOT_V2_MAGIC)
        return -1;
    
    uint32_t calc_hash = tb_config_hash(cfg);
    if (calc_hash != cfg->reserved_hash)
        return -2;

    return 0;
}

uint32_t tb_first_free_sector(void) {
    return tb_first_free_address() / 1024;
}

const struct toboot_configuration *tb_get_config(void) {
    extern uint32_t __app_start__;

    // When examining every application in flash, find the newest program
    // with the highest generation counter.
    uint32_t newest_generation = 0;

    // Fake toboot config, for v1 and v0 programs.
    static struct toboot_configuration fake_config;

    if (current_config)
        return current_config;

    // Look for a V2 header
    uint32_t page;
    for (page = 1; page < 65536/1024; page++) {
        if (!tb_valid_signature_at_page(page)) {
            const struct toboot_configuration *test_cfg = (const struct toboot_configuration *)((page * 1024) + 0x94);
            if (test_cfg->reserved_gen > newest_generation) {
                newest_generation = test_cfg->reserved_gen;
                current_config = test_cfg;
            }
        }
    }

    if (current_config)
        return current_config;

    // No V2 header found, so create one.
    
    // Fake V2 magic
    fake_config.magic = TOBOOT_V2_MAGIC;

    if (((*((uint32_t *)(((uint32_t)&__app_start__) + 0x98))) & TOBOOT_V1_MAGIC_MASK) == TOBOOT_V1_MAGIC)
        // Applications that know about Toboot will indicate their block
        // offset by placing a magic byte at offset 0x98.
        // Ordinarily this would be the address offset for IRQ 22,
        // but since there are only 20 IRQs on the EFM32HG, there are three
        // 32-bit values that are unused starting at offset 0x94.
        // We already use offset 0x94 for "disable boot", so use offset 0x98
        // in the incoming stream to indicate flags for Toboot.
        fake_config.start = ((*((uint32_t *)(((uint32_t)&__app_start__) + 0x98))) & TOBOOT_V1_APP_PAGE_MASK) >> TOBOOT_V1_APP_PAGE_SHIFT;
    else
        // Default to offset 0x4000
        fake_config.start = 16;

    // Leave interrupts enabled (and indicate the header is fake)
    fake_config.config = TOBOOT_CONFIG_FLAG_ENABLE_IRQ | TOBOOT_CONFIG_FAKE;

    // Lock out bootloader entry, if the magic value is present
    if (((*((uint32_t *)(((uint32_t)&__app_start__) + 0x94))) & TOBOOT_V1_CFG_MAGIC_MASK) == TOBOOT_V1_CFG_MAGIC)
        fake_config.lock_entry = TOBOOT_LOCKOUT_MAGIC;
    else
        fake_config.lock_entry = 0;

    // Don't erase anything in particular
    fake_config.erase_mask_lo = 0;
    fake_config.erase_mask_hi = 0;

    // Calculate a valid hash
    tb_sign_config(&fake_config);

    return &fake_config;
}

uint32_t tb_generation(const struct toboot_configuration *cfg) {
    if (!cfg)
        return 0;
    return cfg->reserved_gen;
}

__attribute__ ((used, section(".toboot_configuration"))) struct toboot_configuration toboot_configuration = {
    .magic = TOBOOT_V2_MAGIC,

    // The current "generation" flag sits at the same location as the
    // old Toboot "Config" flag.  By setting "reserved_gen" to this value,
    // we can make the V1 bootloader treat V2 images as valid.
    .reserved_gen = TOBOOT_V1_APP_MAGIC,

    .start = 0,
    .config = 0,

    .lock_entry = 0,
    .erase_mask_lo = 0,
    .erase_mask_hi = 0,
    .reserved_hash = 0,
};
