/* cpu2phys.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* */
/* AS */
/* */
/* CPU->Physical Address Translation */
/* */
/*****************************************************************************/
#include "stdinc.h"
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include "strutil.h"
#include "tempresult.h"
#include "asmdef.h"
#include "cpu2phys.h"
typedef struct
{
LargeWord cpu_start, cpu_end;
LargeWord phys_start, phys_end;
} cpu_2_phys_area_t;
typedef struct
{
cpu_2_phys_area_t *areas;
size_t capacity;
size_t cnt;
LargeWord end;
} cpu_2_phys_area_list_t;
static cpu_2_phys_area_list_t area_lists[SegCount];
/*!------------------------------------------------------------------------
* \fn cpu_2_phys_area_t *insert_area(cpu_2_phys_area_list_t *p_list, unsigned index)
* \brief make space for area at given index, implicitly increasing array size
* \param p_list list to operate on
* \param index place to insert
* \return * to freed and cleared area
* ------------------------------------------------------------------------ */
static cpu_2_phys_area_t *insert_area(cpu_2_phys_area_list_t *p_list, unsigned index)
{
cpu_2_phys_area_t *p_ret;
if (p_list->cnt >= p_list->capacity)
{
size_t new_capacity = p_list->cnt + 1;
size_t new_alloc = new_capacity * sizeof(*p_list->areas);
p_list->areas = p_list->capacity
? (cpu_2_phys_area_t
*) realloc(p_list
->areas
, new_alloc
)
: (cpu_2_phys_area_t
*) malloc(new_alloc
);
p_list->capacity = new_capacity;
}
p_ret = &p_list->areas[index];
p_list->cnt++;
memmove(p_ret
+ 1, p_ret
, sizeof(*p_ret
) * (p_list
->cnt
- 1 - index
));
memset(p_ret
, 0, sizeof(*p_ret
));
return p_ret;
}
/*!------------------------------------------------------------------------
* \fn delete_area(cpu_2_phys_area_list_t *p_list, unsigned index)
* \brief delete entry from table
* \param p_list list to operate on
* \param index place to delete
* ------------------------------------------------------------------------ */
static void delete_area(cpu_2_phys_area_list_t *p_list, unsigned index)
{
cpu_2_phys_area_t *p_ret;
p_ret = &p_list->areas[index];
memmove(p_ret
, p_ret
+ 1, sizeof(*p_ret
) * (p_list
->cnt
- 1 - index
));
p_list->cnt--;
}
/*!------------------------------------------------------------------------
* \fn get_list(as_addrspace_t addr_space)
* \brief retrieve list per address space
* \param addr_space address space
* \return * to list
* ------------------------------------------------------------------------ */
static cpu_2_phys_area_list_t *get_list(as_addrspace_t addr_space)
{
assert(addr_space
< SegCount
);
return &area_lists[addr_space];
}
/*!------------------------------------------------------------------------
* \fn cpu_2_phys_area_clear(as_addrspace_t addr_space)
* \brief clear tables before adding new ones
* \param addr_space address space to operate on
* ------------------------------------------------------------------------ */
void cpu_2_phys_area_clear(as_addrspace_t addr_space)
{
assert(addr_space
< SegCount
);
area_lists[addr_space].cnt = 0;
area_lists[addr_space].end = 0;
}
/*!------------------------------------------------------------------------
* \fn cpu_2_phys_area_add(as_addrspace_t addr_space, LargeWord new_cpu_start, LargeWord new_phys_start, LargeWord new_len)
* \brief add another area
* \param addr_space address space to operate on
* \param new_cpu_start start address in CPU address space
* \param new_phys_start start address in physical address space
* \param new_len area size in <gran>
* ------------------------------------------------------------------------ */
void cpu_2_phys_area_add(as_addrspace_t addr_space, LargeWord new_cpu_start, LargeWord new_phys_start, LargeWord new_len)
{
size_t z, z2;
cpu_2_phys_area_list_t *p_list = get_list(addr_space);
cpu_2_phys_area_t *p_area;
LargeWord new_cpu_end = new_cpu_start + (new_len - 1);
LargeWord overlap_start, overlap_end, delta;
Boolean change;
/* Do not add zero-length areas */
if (!new_len)
return;
/* sort in according to CPU start address */
for (z = 0; z < p_list->cnt; z++)
if (p_list->areas[z].cpu_start >= new_cpu_start)
break;
p_area = insert_area(p_list, z);
p_area->cpu_start = new_cpu_start;
p_area->cpu_end = new_cpu_end;
p_area->phys_start = new_phys_start;
p_area->phys_end = new_phys_start + (new_len - 1);
/* shrink/delete (partially) overlapping areas: */
do
{
change = False;
for (z2 = 0; z2 < p_list->cnt; z2++)
{
/* do not test overlap with new entry itself */
if (z2 == z)
continue;
/* deduce overlapping area */
overlap_start = max(p_list->areas[z].cpu_start, p_list->areas[z2].cpu_start);
overlap_end = min(p_list->areas[z].cpu_end, p_list->areas[z2].cpu_end);
if (overlap_start >= overlap_end)
continue;
/* Delete old area entirely? -> possibly correct index of new entry and restart */
if ((overlap_start == p_list->areas[z2].cpu_start)
&& (overlap_end == p_list->areas[z2].cpu_end))
{
delete_area(p_list, z2);
if (z2 < z)
z--;
change = True;
break;
}
/* shorten old area at beginning? */
else if (overlap_start == p_list->areas[z2].cpu_start)
{
delta = overlap_end - overlap_start + 1;
p_list->areas[z2].cpu_start += delta;
p_list->areas[z2].phys_start += delta;
}
/* shorten old area at end? */
else if (overlap_end == p_list->areas[z2].cpu_end)
{
delta = overlap_end - overlap_start + 1;
p_list->areas[z2].cpu_end -= delta;
p_list->areas[z2].phys_end -= delta;
}
/* Overlap cuts out in the mid, split into parts. Assuming the addresses were sorted,
z2 equals z - 1 and the old area's part surround the new one: */
else
{
cpu_2_phys_area_t save = p_list->areas[z2];
delta = save.cpu_end - overlap_start + 1;
p_list->areas[z2].cpu_end -= delta;
p_list->areas[z2].phys_end -= delta;
p_area = insert_area(p_list, z + 1);
*p_area = save;
delta = overlap_end - save.cpu_start + 1;
p_area->cpu_start += delta;
p_area->phys_start += delta;
change = True;
break;
}
}
}
while (change);
}
/*!------------------------------------------------------------------------
* \fn cpu_2_phys_area_set_cpu_end(as_addrspace_t addr_space, LargeWord cpu_end)
* \brief set the end of the CPU's address space
* \param addr_space address space to operate on
* \param cpu_end end of CPU address space
* ------------------------------------------------------------------------ */
void cpu_2_phys_area_set_cpu_end(as_addrspace_t addr_space, LargeWord cpu_end)
{
cpu_2_phys_area_list_t *p_list = get_list(addr_space);
p_list->end = cpu_end;
}
/*!------------------------------------------------------------------------
* \fn cpu_2_phys_area_fill(as_addrspace_t addr_space, LargeWord cpu_start, LargeWord cpu_end)
* \brief fill gaps in the CPU-side address space with 1:1 mappings
* \param addr_space address space to operate on
* \param cpu_start start of CPU address space
* \param cpu_end end of CPU address space
* ------------------------------------------------------------------------ */
void cpu_2_phys_area_fill(as_addrspace_t addr_space, LargeWord cpu_start, LargeWord cpu_end)
{
LargeWord expected_start;
size_t z;
cpu_2_phys_area_list_t *p_list = get_list(addr_space);
cpu_2_phys_area_t *p_area;
/* For each mapping, check whether there is no gap between its start address
and the predecessor's end address. If so, insert a 1:1 mapped area to
fill the gap: */
z = 0;
while (z < p_list->cnt)
{
expected_start = z ? p_list->areas[z - 1].cpu_end + 1 : cpu_start;
/* no gap -> just continue with the next entry */
if ((z < p_list->cnt) && (p_list->areas[z].cpu_start <= expected_start))
{
z++;
continue;
}
p_area = insert_area(p_list, z);
p_area->cpu_start =
p_area->phys_start = expected_start;
p_area->cpu_end =
p_area->phys_end = (z + 1 < p_list->cnt) ? p_list->areas[z + 1].cpu_start - 1 : cpu_end;
/* We know the entries at z (freshly inserted) and z+1 (moved up one index) are
continuous, so we may increase the counter by two: */
z += 2;
}
/* Do the same test once again at the very end of the array, to be sure it covers everything
up to the given cpu_end: */
if (!p_list->cnt || p_list->areas[p_list->cnt - 1].cpu_end < cpu_end)
{
expected_start = p_list->cnt ? p_list->areas[p_list->cnt - 1].cpu_end + 1 : cpu_start;
p_area = insert_area(p_list, p_list->cnt);
p_area->cpu_start =
p_area->phys_start = expected_start;
p_area->cpu_end =
p_area->phys_end = cpu_end;
}
/* Save the cpu_end for usage in phys_2_cpu(): */
cpu_2_phys_area_set_cpu_end(addr_space, cpu_end);
}
/*!------------------------------------------------------------------------
* \fn cpu_2_phys_area_dump(as_addrspace_t addr_space, FILE *p_file)
* \brief output current mapping
* \param addr_space address space to operate on
* \param p_file where to dump
* ------------------------------------------------------------------------ */
void cpu_2_phys_area_dump(as_addrspace_t addr_space, FILE *p_file)
{
cpu_2_phys_area_list_t *p_list = get_list(addr_space);
char str[100];
size_t z;
int cpu_max_len = 0, phys_max_len = 0;
for (z = 0; z < p_list->cnt; z++)
{
int this_len;
this_len = as_snprintf(str, sizeof str, "%lllx", p_list->areas[z].cpu_end);
if (this_len > cpu_max_len)
cpu_max_len = this_len;
this_len = as_snprintf(str, sizeof str, "%lllx", p_list->areas[z].phys_end);
if (this_len > phys_max_len)
phys_max_len = this_len;
}
for (z = 0; z < p_list->cnt; z++)
{
as_snprintf(str, sizeof str, "[%2u] %0*lllx...%0*lllx -> %0*lllx...%0*lllx\n", z,
cpu_max_len, p_list->areas[z].cpu_start,
cpu_max_len, p_list->areas[z].cpu_end,
phys_max_len, p_list->areas[z].phys_start,
phys_max_len, p_list->areas[z].phys_end);
}
}
/*!------------------------------------------------------------------------
* \fn def_phys_2_cpu(as_addrspace_t addr_space, LargeWord *p_address)
* \brief physical -> CPU address translation
* \param addr_space address space to operate on
* \param p_address address to translate
* \return True if translation succeeded
* ------------------------------------------------------------------------ */
Boolean def_phys_2_cpu(as_addrspace_t addr_space, LargeWord *p_address)
{
cpu_2_phys_area_list_t *p_list = get_list(addr_space);
size_t win;
for (win = 0; win < p_list->cnt; win++)
if ((*p_address >= p_list->areas[win].phys_start) && (*p_address <= p_list->areas[win].phys_end))
{
*p_address = (*p_address - p_list->areas[win].phys_start) + p_list->areas[win].cpu_start;
return True;
}
return False;
}
/*!------------------------------------------------------------------------
* \fn def_cpu_2_phys(as_addrspace_t addr_space, LargeWord *p_address)
* \brief CPU -> physical address translation
* \param addr_space address space to operate on
* \param p_address address to translate
* \return True if translation succeeded
* ------------------------------------------------------------------------ */
Boolean def_cpu_2_phys(as_addrspace_t addr_space, LargeWord *p_address)
{
cpu_2_phys_area_list_t *p_list = get_list(addr_space);
size_t win;
for (win = 0; win < p_list->cnt; win++)
if ((*p_address >= p_list->areas[win].cpu_start) && (*p_address <= p_list->areas[win].cpu_end))
{
*p_address = (*p_address - p_list->areas[win].cpu_start) + p_list->areas[win].phys_start;
return True;
}
return False;
}
/*!------------------------------------------------------------------------
* \fn fnc_phys_2_cpu(TempResult *p_ret, const TempResult *p_args, unsigned arg_cnt)
* \brief built-in function for physical -> CPU address translation
* \param p_ret returns CPU address
* \param p_args physical address argument
* \return True if translation possible at all
* ------------------------------------------------------------------------ */
Boolean fnc_phys_2_cpu(TempResult *p_ret, const TempResult *p_args, unsigned arg_cnt)
{
LargeWord address;
UNUSED(arg_cnt);
if (!area_lists[ActPC].cnt)
return False;
address = p_args[0].Contents.Int;
as_tempres_set_int(p_ret, def_phys_2_cpu(ActPC, &address) ? address : area_lists[ActPC].end + 1);
return True;
}
/*!------------------------------------------------------------------------
* \fn fnc_cpu_2_phys(TempResult *p_ret, const TempResult *p_args, unsigned arg_cnt)
* \brief built-in function for CPU -> physical address translation
* \param p_ret returns physical address
* \param p_args CPU address argument
* \return True if translation possible at all
* ------------------------------------------------------------------------ */
Boolean fnc_cpu_2_phys(TempResult *p_ret, const TempResult *p_args, unsigned arg_cnt)
{
LargeWord address;
UNUSED(arg_cnt);
if (!area_lists[ActPC].cnt)
return False;
address = p_args[0].Contents.Int;
as_tempres_set_int(p_ret, def_cpu_2_phys(ActPC, &address) ? address : SegLimits[ActPC] + 1);
return True;
}