#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
__RCSID("$NetBSD: kvm_m68k_gen.c,v 1.2 2025/12/24 20:37:04 andvar Exp $");
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/kcore.h>
#include <unistd.h>
#include <limits.h>
#include <kvm.h>
#include <stdlib.h>
#include <m68k/cpu.h>
#include <m68k/kcore.h>
#include <m68k/mmu_51.h>
#include <m68k/mmu_30.h>
#include <m68k/mmu_40.h>
#include "kvm_private.h"
#include "kvm_m68k.h"
static int _kvm_gen68k_initvtop(kvm_t *);
static void _kvm_gen68k_freevtop(kvm_t *);
static int _kvm_gen68k_kvatop(kvm_t *, vaddr_t, paddr_t *);
static off_t _kvm_gen68k_pa2off(kvm_t *, u_long);
struct kvm_ops _kvm_ops_gen68k = {
_kvm_gen68k_initvtop,
_kvm_gen68k_freevtop,
_kvm_gen68k_kvatop,
_kvm_gen68k_pa2off
};
struct table_index {
uint32_t ti_shift;
uint32_t ti_mask;
uint32_t ti_pa_mask;
};
#define TABLE_INDEX_COUNT 4
struct kvm_gen68k_context {
uint32_t pg_frame;
uint32_t va_mask;
struct table_index ti[TABLE_INDEX_COUNT];
};
static inline uint32_t
table_entry_paddr(uint32_t table_pa, unsigned int idx)
{
return table_pa + (idx * sizeof(uint32_t));
}
static int
read_table_entry(kvm_t *kd, int level, paddr_t table_pa, unsigned int idx,
uint32_t *entry)
{
uint32_t entry_pa = table_entry_paddr(table_pa, idx);
if (_kvm_pread(kd, kd->pmfd, entry, sizeof(*entry),
_kvm_gen68k_pa2off(kd, entry_pa)) != sizeof(*entry)) {
_kvm_err(kd, 0,
"failed to read level-%c entry @ pa=0x%08x",
'A' + level, entry_pa);
return -1;
}
return 0;
}
static inline unsigned int
table_index(const struct kvm_gen68k_context * const ctx, int level,
uint32_t va)
{
return (va >> ctx->ti[level].ti_shift) & ctx->ti[level].ti_mask;
}
static inline uint32_t
table_entry_extract_pa(const struct kvm_gen68k_context * const ctx, int level,
uint32_t entry)
{
return entry & ctx->ti[level].ti_pa_mask;
}
static int
mmu_tree_walk(kvm_t *kd, uint32_t va, uint32_t *pap)
{
const cpu_kcore_hdr_t * const h = kd->cpu_data;
const struct gen68k_kcore_hdr * const gen = &h->un._gen68k;
const struct vmstate * const vmst = kd->vmst;
const struct kvm_gen68k_context * const ctx = vmst->private;
uint32_t entry, type, table_pa;
uint32_t adj_va = va & ctx->va_mask;
unsigned int idx;
int level;
if (__SHIFTOUT(gen->srp[0], DT51_TYPE) != DT51_SHORT) {
_kvm_err(kd, 0,
"SRP does not point to a SHORT descriptor");
return -1;
}
table_pa = gen->srp[1];
for (level = 0; level < TABLE_INDEX_COUNT; level++) {
idx = table_index(ctx, level, adj_va);
if (read_table_entry(kd, level, table_pa, idx, &entry) < 0) {
return -1;
}
type = __SHIFTOUT(entry, DT51_TYPE);
switch (type) {
case DT51_INVALID:
_kvm_err(kd, 0,
"INVALID level-%c entry @ table_pa=0x%08x idx=%d",
'A' + level, table_pa, idx);
return -1;
case DT51_PAGE:
*pap = entry & ctx->pg_frame;
return 0;
case DT51_SHORT:
table_pa = table_entry_extract_pa(ctx, level, entry);
break;
case DT51_LONG:
default:
_kvm_err(kd, 0,
"LONG level-%c entry @ table_pa=0x%08x idx=%d",
'A' + level, table_pa, idx);
return -1;
}
}
_kvm_err(kd, 0,
"exhausted %d levels for kva=0x%08x\n",
TABLE_INDEX_COUNT, va);
return -1;
}
static int
_kvm_gen68k_initvtop(kvm_t *kd)
{
struct vmstate *vmst = kd->vmst;
struct kvm_gen68k_context *ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
goto bad;
}
cpu_kcore_hdr_t *h = kd->cpu_data;
struct gen68k_kcore_hdr *gen = &h->un._gen68k;
vmst->pgshift = __SHIFTOUT(gen->tcr, TCR51_PS);
if (vmst->pgshift < 8 || vmst->pgshift > 15) {
goto bad;
}
vmst->pgofset = (1U << vmst->pgshift) - 1;
ctx->pg_frame = ~vmst->pgofset;
unsigned int ti[TABLE_INDEX_COUNT];
unsigned int is = __SHIFTOUT(gen->tcr, TCR51_IS);
ti[0] = __SHIFTOUT(gen->tcr, TCR51_TIA);
ti[1] = __SHIFTOUT(gen->tcr, TCR51_TIB);
ti[2] = __SHIFTOUT(gen->tcr, TCR51_TIC);
ti[3] = __SHIFTOUT(gen->tcr, TCR51_TID);
if (is + ti[0] + ti[1] + ti[2] + ti[3] + vmst->pgshift != 32) {
goto bad;
}
if (is > 15 || ti[0] > 15 || ti[1] > 15 || ti[2] > 15 || ti[3] > 15) {
goto bad;
}
ctx->va_mask = 0xffffffffU;
int curshift = 32;
unsigned int count;
if (is) {
ctx->va_mask = (ctx->va_mask << is) >> is;
curshift -= is;
}
for (int i = 0; i < TABLE_INDEX_COUNT; i++) {
if (ti[i] == 0) {
break;
}
curshift -= ti[i];
count = 1U << ti[i];
ctx->ti[i].ti_shift = curshift;
ctx->ti[i].ti_mask = (count - 1);
ctx->ti[i].ti_pa_mask = 0xffffffffU &
~((count * sizeof(uint32_t)) - 1);
}
if (curshift != vmst->pgshift) {
goto bad;
}
vmst->private = ctx;
return 0;
bad:
if (ctx != NULL) {
free(ctx);
}
return -1;
}
static void
_kvm_gen68k_freevtop(kvm_t *kd)
{
struct kvm_gen68k_context *ctx = kd->vmst->private;
kd->vmst->private = NULL;
free(ctx);
}
static int
_kvm_gen68k_kvatop(kvm_t *kd, vaddr_t va, paddr_t *pa)
{
cpu_kcore_hdr_t *h = kd->cpu_data;
const struct gen68k_kcore_hdr * const gen = &h->un._gen68k;
const struct vmstate * const vmst = kd->vmst;
const struct kvm_gen68k_context * const ctx = vmst->private;
uint32_t trans_pa;
if (ISALIVE(kd)) {
_kvm_err(kd, 0, "vatop called in live kernel!");
return 0;
}
vaddr_t offset = va & vmst->pgofset;
va &= ctx->pg_frame;
if (va >= h->kernbase && va < gen->relocend) {
trans_pa = (va - h->kernbase) + gen->reloc;
goto out;
}
if (mmu_tree_walk(kd, va, &trans_pa) < 0) {
return 0;
}
out:
*pa = trans_pa + offset;
return h->page_size - offset;
}
static off_t
_kvm_gen68k_pa2off(kvm_t *kd, u_long pa)
{
cpu_kcore_hdr_t *h = kd->cpu_data;
struct gen68k_kcore_hdr *gen = &h->un._gen68k;
phys_ram_seg_t *rsp = gen->ram_segs;
off_t off = 0;
int i;
for (i = 0; i < GEN68K_NPHYS_RAM_SEGS && rsp[i].size != 0; i++) {
if (pa >= rsp[i].start &&
pa < (rsp[i].start + rsp[i].size)) {
pa -= rsp[i].start;
break;
}
off += rsp[i].size;
}
return (kd->dump_off + off + pa);
}