// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    vtlb.c

    Generic virtual TLB implementation.

***************************************************************************/

#include "i386_opdef.h"

/***************************************************************************
    DEBUGGING
***************************************************************************/

#define PRINTF_TLB          (0)

/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/
/* VTLB state */
struct vtlb_state
{
	void *              cpudevice;          /* CPU device */
	address_spacenum    space;              /* address space */
	int                 dynamic;            /* number of dynamic entries */
	int                 fixed;              /* number of fixed entries */
	int                 dynindex;           /* index of next dynamic entry */
	int                 pageshift;          /* bits to shift to get page index */
	int                 addrwidth;          /* logical address bus width */
	offs_t *            live;               /* array of live entries by table index */
	int *               fixedpages;         /* number of pages each fixed entry covers */
	vtlb_entry *        table;              /* table of entries by address */
	vtlb_entry *        save;               /* cache of live table entries for saving */
};

/***************************************************************************
    INITIALIZATION/TEARDOWN
***************************************************************************/

/*-------------------------------------------------
    vtlb_alloc - allocate a new VTLB for the
    given CPU
-------------------------------------------------*/

vtlb_state *I386_OPS_BASE::vtlb_alloc(void *cpu, address_spacenum space, int fixed_entries, int dynamic_entries)
{
	vtlb_state *vtlb;

	/* allocate memory for the core structure */
	vtlb = (vtlb_state *)calloc(1, sizeof(vtlb_state));

	/* fill in CPU information */
	vtlb->cpudevice = cpu;
	vtlb->space = space;
	vtlb->dynamic = dynamic_entries;
	vtlb->fixed = fixed_entries;
//	const address_space_config *spaceconfig = device_get_space_config(*cpu, space);
//	assert(spaceconfig != NULL);
//	vtlb->pageshift = spaceconfig->m_page_shift;
//	vtlb->addrwidth = spaceconfig->m_logaddr_width;
	/* for i386 */
	vtlb->pageshift = 12;
	vtlb->addrwidth = 32;

	/* validate CPU information */
	assert((1 << vtlb->pageshift) > VTLB_FLAGS_MASK);
	assert(vtlb->addrwidth > vtlb->pageshift);

	/* allocate the entry array */
	vtlb->live = (offs_t *)calloc(fixed_entries + dynamic_entries, sizeof(offs_t));
//	cpu->save_pointer(NAME(vtlb->live), fixed_entries + dynamic_entries, space);

	/* allocate the lookup table */
	vtlb->table = (vtlb_entry *)calloc((size_t) 1 << (vtlb->addrwidth - vtlb->pageshift), sizeof(vtlb_entry));
//	cpu->save_pointer(NAME(vtlb->table), 1 << (vtlb->addrwidth - vtlb->pageshift), space);

	/* allocate the fixed page count array */
	if (fixed_entries > 0)
	{
		vtlb->fixedpages = (int *)calloc(fixed_entries, sizeof(int));
//		cpu->save_pointer(NAME(vtlb->fixedpages), fixed_entries, space);
	}
	return vtlb;
}


/*-------------------------------------------------
    vtlb_free - free an allocated VTLB
-------------------------------------------------*/

void I386_OPS_BASE::vtlb_free(vtlb_state *vtlb)
{
	/* free the fixed pages if allocated */
	if (vtlb->fixedpages != NULL)
		free(vtlb->fixedpages);

	/* free the table and array if they exist */
	if (vtlb->live != NULL)
		free(vtlb->live);
	if (vtlb->table != NULL)
		free(vtlb->table);

	/* and then the VTLB object itself */
	free(vtlb);
}



/***************************************************************************
    FILLING
***************************************************************************/

/*-------------------------------------------------
    vtlb_fill - rcalled by the CPU core in
    response to an unmapped access
-------------------------------------------------*/

int I386_OPS_BASE::vtlb_fill(vtlb_state *vtlb, offs_t address, int intention)
{
	offs_t tableindex = address >> vtlb->pageshift;
	vtlb_entry entry = vtlb->table[tableindex];
	offs_t taddress;

//	if (PRINTF_TLB)
//		printf("vtlb_fill: %08X(%X) ... ", address, intention);

	/* should not be called here if the entry is in the table already */
//  assert((entry & (1 << intention)) == 0);

	/* if we have no dynamic entries, we always fail */
	if (vtlb->dynamic == 0)
	{
//		if (PRINTF_TLB)
//			printf("failed: no dynamic entries\n");
		return FALSE;
	}

	/* ask the CPU core to translate for us */
	taddress = address;
//	if (!vtlb->cpudevice->translate(vtlb->space, intention, taddress))
	if (!CPU_TRANSLATE_NAME(i386)(vtlb->cpudevice, vtlb->space, intention, &taddress))
	{
//		if (PRINTF_TLB)
//			printf("failed: no translation\n");
		return FALSE;
	}

	/* if this is the first successful translation for this address, allocate a new entry */
	if ((entry & VTLB_FLAGS_MASK) == 0)
	{
		int liveindex = vtlb->dynindex++ % vtlb->dynamic;

		/* if an entry already exists at this index, free it */
		if (vtlb->live[liveindex] != 0)
			vtlb->table[vtlb->live[liveindex] - 1] = 0;

		/* claim this new entry */
		vtlb->live[liveindex] = tableindex + 1;

		/* form a new blank entry */
		entry = (taddress >> vtlb->pageshift) << vtlb->pageshift;
		entry |= VTLB_FLAG_VALID;

//		if (PRINTF_TLB)
//			printf("success (%08X), new entry\n", taddress);
	}

	/* otherwise, ensure that different intentions do not produce different addresses */
	else
	{
		assert((entry >> vtlb->pageshift) == (taddress >> vtlb->pageshift));
		assert(entry & VTLB_FLAG_VALID);

//		if (PRINTF_TLB)
//			printf("success (%08X), existing entry\n", taddress);
	}

	/* add the intention to the list of valid intentions and store */
	entry |= 1 << (intention & (TRANSLATE_TYPE_MASK | TRANSLATE_USER_MASK));
	vtlb->table[tableindex] = entry;
	return TRUE;
}


/*-------------------------------------------------
    vtlb_load - load a fixed VTLB entry
-------------------------------------------------*/

void I386_OPS_BASE::vtlb_load(vtlb_state *vtlb, int entrynum, int numpages, offs_t address, vtlb_entry value)
{
	offs_t tableindex = address >> vtlb->pageshift;
	int liveindex = vtlb->dynamic + entrynum;
	int pagenum;

	/* must be in range */
	assert(entrynum >= 0 && entrynum < vtlb->fixed);

//	if (PRINTF_TLB)
//		printf("vtlb_load %d for %d pages at %08X == %08X\n", entrynum, numpages, address, value);

	/* if an entry already exists at this index, free it */
	if (vtlb->live[liveindex] != 0)
	{
		int pagecount = vtlb->fixedpages[entrynum];
		int oldtableindex = vtlb->live[liveindex] - 1;
		for (pagenum = 0; pagenum < pagecount; pagenum++)
			vtlb->table[oldtableindex + pagenum] = 0;
	}

	/* claim this new entry */
	vtlb->live[liveindex] = tableindex + 1;

	/* store the raw value, making sure the "fixed" flag is set */
	value |= VTLB_FLAG_FIXED;
	vtlb->fixedpages[entrynum] = numpages;
	for (pagenum = 0; pagenum < numpages; pagenum++)
		vtlb->table[tableindex + pagenum] = value + (pagenum << vtlb->pageshift);
}

/*-------------------------------------------------
    vtlb_dynload - load a dynamic VTLB entry
-------------------------------------------------*/

void I386_OPS_BASE::vtlb_dynload(vtlb_state *vtlb, UINT32 index, offs_t address, vtlb_entry value)
{
	vtlb_entry entry = vtlb->table[index];

	if (vtlb->dynamic == 0)
	{
//		if (PRINTF_TLB)
//			printf("failed: no dynamic entries\n");
		return;
	}

	int liveindex = vtlb->dynindex++ % vtlb->dynamic;
	/* is entry already live? */
	if (!(entry & VTLB_FLAG_VALID))
	{
		/* if an entry already exists at this index, free it */
		if (vtlb->live[liveindex] != 0)
			vtlb->table[vtlb->live[liveindex] - 1] = 0;

		/* claim this new entry */
		vtlb->live[liveindex] = index + 1;
	}
	/* form a new blank entry */
	entry = (address >> vtlb->pageshift) << vtlb->pageshift;
	entry |= VTLB_FLAG_VALID | value;

//	if (PRINTF_TLB)
//		printf("success (%08X), new entry\n", address);

	vtlb->table[index] = entry;
}

/***************************************************************************
    FLUSHING
***************************************************************************/

/*-------------------------------------------------
    vtlb_flush_dynamic - flush all knowledge
    from the dynamic part of the VTLB
-------------------------------------------------*/

void I386_OPS_BASE::vtlb_flush_dynamic(vtlb_state *vtlb)
{
	int liveindex;

//	if (PRINTF_TLB)
//		printf("vtlb_flush_dynamic\n");

	/* loop over live entries and release them from the table */
	for (liveindex = 0; liveindex < vtlb->dynamic; liveindex++)
		if (vtlb->live[liveindex] != 0)
		{
			offs_t tableindex = vtlb->live[liveindex] - 1;
			vtlb->table[tableindex] = 0;
			vtlb->live[liveindex] = 0;
		}
}


/*-------------------------------------------------
    vtlb_flush_address - flush knowledge of a
    particular address from the VTLB
-------------------------------------------------*/

void I386_OPS_BASE::vtlb_flush_address(vtlb_state *vtlb, offs_t address)
{
	offs_t tableindex = address >> vtlb->pageshift;

//	if (PRINTF_TLB)
//		printf("vtlb_flush_address %08X\n", address);

	/* free the entry in the table; for speed, we leave the entry in the live array */
	vtlb->table[tableindex] = 0;
}



/***************************************************************************
    ACCESSORS
***************************************************************************/

/*-------------------------------------------------
    vtlb_table - return a pointer to the base of
    the linear VTLB lookup table
-------------------------------------------------*/

const vtlb_entry *I386_OPS_BASE::vtlb_table(vtlb_state *vtlb)
{
	return vtlb->table;
}
