#include "common.h"
#include "io.h"
#include "abcio.h"
#include "sys.h"
#include "console.h"
#include "config.h"
#include "roms.h"

/* Configure ABC memory map */
struct data_len {
    const char *data;
    size_t len;
};
struct abc_mem_init {
    int addr;
    uint32_t flags;
    enum sysvar_enum enable;
    struct data_len dl;
};
#define RD ABCMEMMAP_RD
#define WR ABCMEMMAP_WR

#define K 1024
#define DD(x)	{ (x), sizeof(x) }
#define END { -1, 0, sysvar_null, { NULL, 0 } }

/* ---- ABC80 memory configurations ---- */

/* Not really NV, but matches NVRAM in some expansions */
static char __dram_bss __aligned(512) abc80_nvram[2][2*K];

/* 16K external memory to expand to 512K */
static char __dram_bss __aligned(512) abc80_extmem[16*K];

static const struct abc_mem_init mem_init_abc80[] = {
    /* Put these here in case someone wants to enable both */
    { 20*K, RD|WR, config_abc_mem_abc80_nvram_20k,  DD(abc80_nvram[0]) },
    { 22*K, RD|WR, config_abc_mem_abc80_nvram_22k,  DD(abc80_nvram[1]) },

    { 16*K, RD,    config_abc_mem_abc80_smartaid3,  DD(rom_abc80_smartaid3) },
    { 16*K, RD,    config_abc_mem_abc80_superbasic, DD(rom_abc80_superbasic16k) },
    { 20*K, RD,    config_abc_mem_abc80_abc80l,     DD(rom_abc80_abc80l) },
    { 28*K, RD,    config_abc_mem_abc80_superbasic, DD(rom_abc80_superbasic28k) },
    { 32*K, RD|WR, config_abc_mem_abc80_ram,        DD(abc80_extmem) },
    END
};
static const struct abc_mem_init mem_init_abc80_nvram_20k[] = {
    { 24*K, RD,    config_abc_mem_abc80_ufddos,	    DD(rom_abc80_nvram_20k_ufddos80) },
    { 28*K, RD,    config_abc_mem_abc80_pun80_28k,  DD(rom_abc80_nvram_20k_print80_28) },
    { 29*K, RD,    config_abc_mem_abc80_pun80_29k,  DD(rom_abc80_nvram_20k_print80_29) },
    { 30*K, RD,    config_abc_mem_abc80_pun80_30k,  DD(rom_abc80_nvram_20k_print80_30) },
    END
};
static const struct abc_mem_init mem_init_abc80_nvram_22k[] = {
    { 24*K, RD,    config_abc_mem_abc80_ufddos,	    DD(rom_abc80_nvram_22k_ufddos80) },
    { 28*K, RD,    config_abc_mem_abc80_pun80_28k,  DD(rom_abc80_nvram_22k_print80_28) },
    { 29*K, RD,    config_abc_mem_abc80_pun80_29k,  DD(rom_abc80_nvram_22k_print80_29) },
    { 30*K, RD,    config_abc_mem_abc80_pun80_30k,  DD(rom_abc80_nvram_22k_print80_30) },
    END
};
static const struct abc_mem_init mem_init_abc80_no_nvram[] = {
    { 24*K, RD,    config_abc_mem_abc80_ufddos,	    DD(rom_abc80_no_nvram_ufddos80) },
    { 28*K, RD,    config_abc_mem_abc80_pun80_28k,  DD(rom_abc80_no_nvram_print80_28) },
    { 29*K, RD,    config_abc_mem_abc80_pun80_29k,  DD(rom_abc80_no_nvram_print80_29) },
    { 30*K, RD,    config_abc_mem_abc80_pun80_30k,  DD(rom_abc80_no_nvram_print80_30) },
    END
};

/* ---- ABC800 memory configurations ---- */

static const struct abc_mem_init mem_init_abc800[] = {
    { 24*K, RD,    config_abc_mem_abc800_ufddos,    DD(rom_abc800_ufddos) },
    END
};

#define ABC_PAGE_SHIFT	9
#define ABC_PAGE_SIZE	(1U << ABC_PAGE_SHIFT)
#define ABC_PAGE_MASK	(0xffff & ~(ABC_PAGE_SIZE-1))
#define ABC_PAGE_COUNT	(0x10000 >> ABC_PAGE_SHIFT)

static void abc_map_list(uint32_t *memmap, const struct abc_mem_init *mem)
{
    while (mem->addr >= 0) {
	bool enabled = !mem->enable || getvar_bool(mem->enable);
	const void *data = mem->dl.data;
	size_t len = mem->dl.len;

	if (!len)
	    continue;		/* Empty range, skip */

	uint32_t addr = mem->addr;
	bool bad = ((addr | len) & ~ABC_PAGE_MASK) ||
	    len > 0x10000 - addr ||
	    (size_t)data & (ABC_PAGE_SIZE-1);

	con_printf("abcmem: %s memory range @ 0x%04x len 0x%04x\n",
		   bad ? "invalid" : !enabled ? "ignoring" :
		   data ? "mapping" : "unmapping",
		   addr, len);

	if (enabled) {
	    uint32_t *pg = &memmap[addr >> ABC_PAGE_SHIFT];
	    uint32_t flags = mem->flags;

	    if (!data) {
		/* Mapped range */
		for (uint32_t bytes = 0; bytes < len; bytes += ABC_PAGE_SIZE)
		    *pg++ = ((size_t)(data + bytes) & SDRAM_MASK) | flags;
	    } else {
		/* Unmapped range - set to 0 for clarity */
		memset(pg, 0, len >> (ABC_PAGE_SHIFT-2));
	    }
	}
	mem++;
    }
}

void __cold abc_init_memmap(void)
{
    uint32_t *memmap = calloc(sizeof(uint32_t), ABC_PAGE_COUNT);
    const struct abc_mem_init *mem;

    if (!memmap) {
	con_printf("abcmem: memory map initialization failure\n");
	return;
    }

    if (is_abc800()) {
	abc_map_list(memmap, mem_init_abc800);
    } else {
	/* Put 22K first so UFD-DOS/PUN80 memory always is put at the end */
	if (getvar_bool(config_abc_mem_abc80_nvram_22k))
	    abc_map_list(memmap, mem_init_abc80_nvram_22k);
	else if (getvar_bool(config_abc_mem_abc80_nvram_20k))
	    abc_map_list(memmap, mem_init_abc80_nvram_20k);
	else
	    abc_map_list(memmap, mem_init_abc80_no_nvram);

	abc_map_list(memmap, mem_init_abc80);
    }

    /* Install memory map into hardware registers */
    memcpy((void *)&ABCMEMMAP_PAGE(0), memmap,
	   ABC_PAGE_COUNT*sizeof(uint32_t));
    free(memmap);
}
