#include "common.h"
#include "boardinfo_esp.h"

#include <esp_attr.h>
#include <esp_flash.h>
#include <esp_spi_flash.h>
#include <esp_flash_internal.h>	/* For esp_flash_app_disable_protect() */
#include <rom/crc.h>

#define BOARDINFO_ADDR	0	/* Flash address on ESP */

static const union board_info_block *board_info_flash;
struct board_info DRAM_ATTR board_info;

static spi_flash_mmap_handle_t board_info_handle;

static const union board_info_block *board_info_map(void)
{
    if (!board_info_flash) {
	const void *bifp;

	if (spi_flash_mmap(BOARDINFO_ADDR, BOARDINFO_SIZE, SPI_FLASH_MMAP_DATA,
			   &bifp, &board_info_handle)
	    == ESP_OK)
	    board_info_flash = bifp;
    }
    return board_info_flash;
}

static void board_info_unmap(void)
{
    if (board_info_flash) {
	spi_flash_munmap(board_info_handle);
	board_info_flash = NULL;
	board_info_handle = 0;
    }
}

/* Returns 1 if the flash was modified, 0 if unmodified, -1 on error */
int board_info_init(void)
{
    int err = -1;
    uint32_t crc, bif_crc;
    const union board_info_block *bif;

    bif = board_info_map();
    if (!bif)
	goto unmapped;

    if (bif->i.magic[0] != BOARDINFO_MAGIC_1 ||
	bif->i.magic[1] != BOARDINFO_MAGIC_2 ||
	bif->i.len < 16 || bif->i.len > BOARDINFO_SIZE ||
	board_info.version_str[sizeof board_info.version_str - 1])
	goto bad;

    memcpy(&board_info, bif, sizeof board_info);

    bif_crc = board_info.crc;
    board_info.crc = 0;
    crc = crc32_le(0, (const uint8_t *)&board_info, 16);
    crc = crc32_le(crc, &bif->b[16], bif->i.len - 16);
    board_info.crc = bif_crc;

    if (crc != bif_crc) {
	printf("[PCB]  Bad board_info crc %08x calculated %08x\n",
	       bif_crc, crc);
	goto bad;
    }

    printf("[PCB]  Board ID/version: %s\n", board_info.version_str);

    err = 0;
    goto done;

bad:
    if (!memcmp(board_info_flash->b, "MAX80 ", 6) &&
	strnlen(board_info_flash->b, sizeof board_info.version_str)
	< sizeof board_info.version_str) {
	/*
	 * Contains board version string but nothing else; this
	 * is allowed to simplify the initial programming.
	 * Convert it to a proper structure and write it back.
	 */
	printf("[PCB]  updating board information block in flash\n");
	return board_info_set((const char *)board_info_flash->b);
    }

unmapped:
    printf("WARNING: no board ID/version string set in flash\n");
    board_info.len  = 0;
    
done:
    if (board_info.len < sizeof board_info)
	memset((char *)&board_info + board_info.len, 0,
	       sizeof board_info - board_info.len);

    board_info_unmap();
    return err;
}

static int board_info_generate(const char *board_id_string)
{
    memset(&board_info, 0, sizeof board_info);
    board_info.magic[0] = BOARDINFO_MAGIC_1;
    board_info.magic[1] = BOARDINFO_MAGIC_2;
    board_info.len      = sizeof board_info;

    strncpy(board_info.version_str, board_id_string,
	    sizeof board_info.version_str - 1);

    memcpy(board_info.mac[0], efuse_default_mac, 6);

    board_info.crc = crc32_le(0, (const uint8_t *)&board_info,
			      sizeof board_info);

    return 0;			/* For tailcalling convenience */
}

/* Must be in IRAM to allow flash operations */
static int __noinline IRAM_ATTR board_info_update_flash(void)
{
    esp_err_t err;
    int rv = -1;

    /* The board_info table is in protected memory */
    err = esp_flash_app_disable_protect(true);
    if (err) {
	printf("board_info_set: failed to unprotect flash (err 0x%x)\n", err);
	goto err1;
    }
    
    err = esp_flash_erase_region(NULL, BOARDINFO_ADDR, BOARDINFO_SIZE);
    if (err) {
	printf("board_info_set: failed to erase board info flash region (err 0x%x)\n", err);
	goto err2;
    }

    err = esp_flash_write(NULL, &board_info, BOARDINFO_ADDR, sizeof board_info);
    if (err) {
	printf("board_info_set: failed to write board info flash region (err 0x%x)\n", err);
	goto err2;
    }

    rv = 0;
    
err2:
    esp_flash_app_disable_protect(false);
err1:
    return rv;
}

int board_info_set(const char *board_id_string)
{
    const union board_info_block *bif;

    board_info_generate(board_id_string);
    bif = board_info_map();
    if (!bif)
	return -1;		/* Could not map board info */
    
    bool unchanged = !memcmp(bif, &board_info, sizeof board_info);
    board_info_unmap();

    if (unchanged)
	return 0;		/* Not modified, so don't reflash */

    return board_info_update_flash();
}