/*	$NetBSD: machdep.c,v 1.233 2026/04/28 03:29:11 thorpej Exp $	*/

/*
 * Copyright (c) 1988 University of Utah.
 * Copyright (c) 1982, 1986, 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Systems Programming Group of the University of Utah Computer
 * Science Department.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * from: Utah $Hdr: machdep.c 1.74 92/12/20$
 *
 *	@(#)machdep.c	8.10 (Berkeley) 4/20/94
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: machdep.c,v 1.233 2026/04/28 03:29:11 thorpej Exp $");

#include "opt_ddb.h"
#include "opt_kgdb.h"
#include "opt_fpu_emulate.h"
#include "opt_m060sp.h"
#include "opt_modular.h"
#include "opt_panicbutton.h"
#include "opt_extmem.h"
#include "opt_m68k_arch.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/signalvar.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/reboot.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/mbuf.h>
#include <sys/msgbuf.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/mount.h>
#include <sys/exec.h>
#include <sys/vnode.h>
#include <sys/syscallargs.h>
#include <sys/core.h>
#include <sys/kcore.h>
#include <sys/ksyms.h>
#include <sys/module.h>
#include <sys/cpu.h>
#include <sys/sysctl.h>
#include <sys/device.h>

#include "ksyms.h"

#if NKSYMS || defined(DDB) || defined(MODULAR)
#include <sys/exec_elf.h>
#endif

#include <machine/db_machdep.h>
#include <ddb/db_sym.h>
#include <ddb/db_extern.h>

#include <m68k/cacheops.h>
#include <machine/reg.h>
#include <machine/pcb.h>
#include <machine/psl.h>
#include <machine/pte.h>
#include <machine/kcore.h>

#include <m68k/seglist.h>

#include <dev/cons.h>
#include <dev/mm.h>

#define	MAXMEM	64*1024	/* XXX - from cmap.h */
#include <uvm/uvm.h>
#include <uvm/uvm_physseg.h>

#include <machine/bus.h>
#include <machine/autoconf.h>
#include <arch/x68k/dev/intiovar.h>
#include <arch/x68k/x68k/iodevice.h>

extern void doboot(void) __attribute__((__noreturn__));

extern paddr_t avail_start, avail_end;
extern u_int lowram;
extern int end, *esym;

int	maxmem;			/* max memory per process */

/* prototypes for local functions */
static int check_emulator(char *, int);

/* functions called from locore.s */
void	machine_init(paddr_t);
void	nmihand(struct frame);
void	intrhand(int);

/* memory probe function called from locore.s */
void	setmemrange(void);
#ifdef EXTENDED_MEMORY
static bool mem_exists(paddr_t, paddr_t);
#endif

static int basemem;

/*
 * Assume a standard X68030 with 25MHz CPU.  The delay_divisor will
 * be calibrated later.
 */
int	delay_divisor = delay_divisor_est(25);


/*
 * machine_bootmap[] is checked in pmap_bootstrap1() of the new m68k pmap
 * and it allocates kernel address space for intio devices.
 */
static vaddr_t intiova;
#define PMBM_INTIO	0
struct pmap_bootmap machine_bootmap[] = {
	{ .pmbm_vaddr_ptr = &intiova,
	  .pmbm_paddr = INTIOBASE,
	  .pmbm_size  = INTIOSIZE,
	  .pmbm_flags = PMBM_F_CI },
	{ .pmbm_vaddr = -1 },
};

static callout_t candbtimer_ch;

void
machine_init(paddr_t nextpa)
{
	extern paddr_t msgbufpa;

#ifdef __HAVE_NEW_PMAP_68K
	/* load the internal IO space region */
	intiobase = (uint8_t *)intiova;
#endif

	/*
	 * Most m68k ports allocate msgbuf at the end of available memory
	 * (i.e. just after avail_end), but on x68k we allocate msgbuf
	 * at the end of main memory for compatibility.
	 *
	 * (This means we have to fully initialize the first phys seg
	 * entry.)
	 */
	phys_seg_list[0].ps_start = phys_seg_list[0].ps_avail_start =
	    m68k_round_page(phys_seg_list[0].ps_start);
	phys_seg_list[0].ps_end = phys_seg_list[0].ps_avail_end =
	    m68k_trunc_page(phys_seg_list[0].ps_end);

	phys_seg_list[0].ps_avail_end -= m68k_round_page(MSGBUFSIZE);
	msgbufpa = phys_seg_list[0].ps_avail_end;

	machine_init_common(nextpa);
}

/*
 * Console initialization: called early on from main,
 * before vm init or startup.  Do enough configuration
 * to choose and initialize a console.
 */
void
consinit(void)
{

	/*
	 * bring graphics layer up.
	 */
	config_console();

	/*
	 * Initialize the console before we print anything out.
	 */
	cninit();

#ifdef KGDB
	zs_kgdb_init();			/* XXX */
#endif
#if NKSYMS || defined(DDB) || defined(MODULAR)
	ksyms_addsyms_elf((int)esym - (int)&end - sizeof(Elf32_Ehdr),
	    (void *)&end, esym);
#endif
#ifdef DDB
	if (boothowto & RB_KDB)
		Debugger();
#endif
}

/*
 * cpu_startup: allocate memory for variable-sized tables,
 * initialize cpu, and do autoconfiguration.
 */
void
cpu_startup(void)
{
	cpu_startup_common();

	callout_init(&candbtimer_ch, 0);
}

void
machine_set_model(void)
{
	/* there's alot of XXX in here... */
	const char *mach;
	char emubuf[20];

	/*
	 * check machine type constant
	 */
	switch (intio_get_sysport_mpustat()) {
	case 0xdc:
		/*
		 * CPU Type == 68030, Clock == 25MHz
		 */
		mach = "030";
		break;
	case 0xfe:
		/*
		 * CPU Type == 68000, Clock == 16MHz
		 */
		mach = "000XVI";
		break;
	case 0xff:
		/*
		 * CPU Type == 68000, Clock == 10MHz
		 */
		mach = "000/ACE/PRO/EXPERT/SUPER";
		break;
	default:
		/*
		 * unknown type
		 */
		mach = "000?(unknown model)";
		break;
	}

	emubuf[0] = '\0';
	check_emulator(emubuf, sizeof(emubuf));

	switch (cputype) {
	case CPU_68060:
		/*
		 * This delay_divisor method cannot get accurate cpuspeed
		 * for 68060.
		 */
		/* XXX really? */
		break;
	case CPU_68040:
		cpuspeed_khz = delay_divisor_est40(delay_divisor) * 1000;
		break;
	case CPU_68030:
		cpuspeed_khz = delay_divisor_est(delay_divisor) * 1000;
		break;
	case CPU_68020:
		cpuspeed_khz = delay_divisor_est(delay_divisor) * 1000;
		break;
	default:
		panic("impossible cpu");
	}

	cpu_setmodel("X68%s %s%s",
	    mach, emubuf[0] ? " on " : "", emubuf);
}

/*
 * If it is an emulator, store the name in buf and return 1.
 * Otherwise return 0.
 */
static int
check_emulator(char *buf, int bufsize)
{
	int xm6major;
	int xm6minor;
	int xm6imark;
	int xm6imajor;
	int xm6iminor;

	/* XM6 and its family */
	intio_set_sysport_sramwp('X');
	if (intio_get_sysport_sramwp() == '6') {
		xm6major = intio_get_sysport_sramwp();
		xm6minor = intio_get_sysport_sramwp();
		xm6imark = intio_get_sysport_sramwp();
		switch (xm6imark) {
		case 0xff:	/* Original XM6 or unknown compatibles */
			snprintf(buf, bufsize, "XM6 v%x.%02x",
				xm6major, xm6minor);
			break;

		case 'i':	/* XM6i */
			xm6imajor = intio_get_sysport_sramwp();
			xm6iminor = intio_get_sysport_sramwp();
			snprintf(buf, bufsize, "XM6i v%d.%02d",
				xm6imajor, xm6iminor);
			break;

		case 'g':	/* XM6 TypeG */
			snprintf(buf, bufsize, "XM6 TypeG v%x.%02x",
				xm6major, xm6minor);
			break;

		default:	/* Other XM6 compatibles? */
			/* XXX what should I do? */
			return 0;
		}
		return 1;
	}

	return 0;
}

/*
 * machine dependent system variables.
 */
SYSCTL_SETUP(sysctl_machdep_setup, "sysctl machdep subtree setup")
{

	sysctl_createv(clog, 0, NULL, NULL,
	    CTLFLAG_PERMANENT,
	    CTLTYPE_NODE, "machdep", NULL,
	    NULL, 0, NULL, 0,
	    CTL_MACHDEP, CTL_EOL);

	sysctl_createv(clog, 0, NULL, NULL,
	    CTLFLAG_PERMANENT,
	    CTLTYPE_STRUCT, "console_device", NULL,
	    sysctl_consdev, 0, NULL, sizeof(dev_t),
	    CTL_MACHDEP, CPU_CONSDEV, CTL_EOL);
}

void
machine_powerdown(void)
{
	printf("powering off...\n");
	delay(1000000);

	/* Turn off the alarm signal of RTC */
	IODEVbase->io_rtc.bank0.reset = 0x0c;

	intio_set_sysport_powoff(0x00);
	intio_set_sysport_powoff(0x0f);
	intio_set_sysport_powoff(0x0f);
	delay(1000000);
}

void
intrhand(int sr)
{

	printf("intrhand: unexpected sr 0x%x\n", sr);
}

const uint16_t ipl2psl_table[NIPL] = {
	[IPL_NONE]       = PSL_S | PSL_IPL0,
	[IPL_SOFTCLOCK]  = PSL_S | PSL_IPL1,
	[IPL_SOFTBIO]    = PSL_S | PSL_IPL1,
	[IPL_SOFTNET]    = PSL_S | PSL_IPL1,
	[IPL_SOFTSERIAL] = PSL_S | PSL_IPL1,
	[IPL_VM]         = PSL_S | PSL_IPL5,
	[IPL_SCHED]      = PSL_S | PSL_IPL7,
	[IPL_HIGH]       = PSL_S | PSL_IPL7,
};

#if (defined(DDB) || defined(DEBUG)) && !defined(PANICBUTTON)
#define PANICBUTTON
#endif

#ifdef PANICBUTTON
int panicbutton = 1;	/* non-zero if panic buttons are enabled */
int crashandburn = 0;
int candbdelay = 50;	/* give em half a second */
void candbtimer(void *);

void
candbtimer(void *arg)
{

	crashandburn = 0;
}
#endif

/*
 * Level 7 interrupts can be caused by the NMI button.
 */
void
nmihand(struct frame frame)
{

	intio_set_sysport_keyctrl(intio_get_sysport_keyctrl() | 0x04);

	if (1) {
#ifdef PANICBUTTON
		static int innmihand = 0;

		/*
		 * Attempt to reduce the window of vulnerability for recursive
		 * NMIs (e.g. someone holding down the keyboard reset button).
		 */
		if (innmihand == 0) {
			innmihand = 1;
			printf("NMI button pressed\n");
			innmihand = 0;
		}
#ifdef DDB
		Debugger();
#else
		if (panicbutton) {
			if (crashandburn) {
				crashandburn = 0;
				panic(panicstr ?
				    "forced crash, nosync" : "forced crash");
			}
			crashandburn++;
			callout_reset(&candbtimer_ch, candbdelay,
			    candbtimer, NULL);
		}
#endif /* DDB */
#endif /* PANICBUTTON */
		return;
	}
	/* panic?? */
	printf("unexpected level 7 interrupt ignored\n");
}

#ifdef EXTENDED_MEMORY

static const struct memlist {
	paddr_t exstart;
	psize_t minsize;
	psize_t maxsize;
} memlist[] = {
	/* We define two extended memory regions for all possible settings. */

	/*
	 * The first region is at 0x01000000:
	 *
	 *  TS-6BE16:	16MB at 0x01000000 (to 0x01FFFFFF)
	 *  XM6i:	4MB - 240MB at 0x01000000 (upto 0x0FFFFFFF)
	 */
	{ 0x01000000, 0x00400000, 0x0f000000 },

	/*
	 * The second region is at 0x10000000:
	 *
	 * 060turbo:	4MB - 128MB at 0x10000000 (upto 0x17FFFFFF)
	 * XM6i:	4MB - 768MB at 0x10000000 (upto 0x3FFFFFFF)
	 */
	{ 0x10000000, 0x00400000, 0x30000000 },
};

/* check each 4MB region */
#define EXTMEM_RANGE	(4 * 1024 * 1024)

__CTASSERT(__arraycount(memlist) + 1 <= VM_PHYSSEG_MAX);

/*
 * check memory existency
 */
static bool
mem_exists(paddr_t mem, paddr_t basemax)
{
	/* most variables must be register! */
	volatile unsigned char *m, *b;
	unsigned char save_m, save_b=0;	/* XXX: shutup gcc */
	bool baseismem;
	bool exists = false;
	void *base;
	void *begin_check, *end_check;
	label_t	faultbuf;

	/*
	 * In this function we assume:
	 *  - MMU is not enabled yet but PA == VA
	 *    (i.e. no RELOC() macro to convert PA to VA)
	 *  - bus error can be caught by setjmp()
	 *    (i.e. %vbr register is initialized properly)
	 *  - all memory cache is not enabled
	 */

	/* only 24bits are significant on normal X680x0 systems */
	base = (void *)(mem & 0x00FFFFFF);

	m = (void *)mem;
	b = (void *)base;

	/* This is somewhat paranoid -- avoid overwriting myself */
	__asm("lea %%pc@(begin_check_mem),%0" : "=a"(begin_check));
	__asm("lea %%pc@(end_check_mem),%0" : "=a"(end_check));
	if (base >= begin_check && base < end_check) {
		size_t off = (char *)end_check - (char *)begin_check;

		m -= off;
		b -= off;
	}

	nofault = &faultbuf;
	if (setjmp(nofault)) {
		nofault = NULL;
		return false;
	}

	(void)*m;
	/*
	 * Can't check by writing if the corresponding
	 * base address isn't memory.
	 *
	 * I hope this would be no harm....
	 */
	baseismem = base < (void *)basemax;

__asm("begin_check_mem:");
	/* save original value (base must be saved first) */
	if (baseismem)
		save_b = *b;
	save_m = *m;

	/*
	 * stack and other data segment variables are unusable
	 * til end_check_mem, because they may be clobbered.
	 */

	/*
	 * check memory by writing/reading
	 */
	if (baseismem)
		*b = 0x55;
	*m = 0xAA;
	if ((baseismem && *b != 0x55) || *m != 0xAA)
		goto out;

	*m = 0x55;
	if (baseismem)
		*b = 0xAA;
	if (*m != 0x55 || (baseismem && *b != 0xAA))
		goto out;

	exists = true;
out:
	*m = save_m;
	if (baseismem)
		*b = save_b;

__asm("end_check_mem:");

	nofault = NULL;

	return exists;
}
#endif

void
setmemrange(void)
{
#ifdef EXTENDED_MEMORY
	int i;
	paddr_t exstart, exend;
	psize_t size, minsize, maxsize;
	const struct memlist *mlist = memlist;
	paddr_t basemax = m68k_ptob(physmem);
#endif

	/*
	 * VM system is not started yet, and even MMU is not enabled here.
	 * We assume VA == PA and don't bother to use RELOC() macro
	 * as pmap_bootstrap() does.
	 */

	/* save the original base memory range */
	basemem = physmem;

	/*
	 * XXX
	 * Probably we should also probe the main memory region
	 * for machines which might have a wrong value in a dead SRAM.
	 */
	phys_seg_list[0].ps_start = lowram;
	phys_seg_list[0].ps_end = lowram + m68k_ptob(basemem);

#ifdef EXTENDED_MEMORY
	/* discover extended memory */
	for (i = 0; i < __arraycount(memlist); i++) {
		exstart = mlist[i].exstart;
		minsize = mlist[i].minsize;
		maxsize = mlist[i].maxsize;
		/*
		 * Normally, x68k hardware is NOT 32bit-clean.
		 * But some type of extended memory is in 32bit address space.
		 * Check whether.
		 */
		if (!mem_exists(exstart, basemax))
			continue;
		exend = 0;
		/* range check */
		for (size = minsize; size <= maxsize; size += EXTMEM_RANGE) {
			if (!mem_exists(exstart + size - 4, basemax))
				break;
			exend = exstart + size;
		}
		if (exstart < exend) {
			phys_seg_list[i + 1].ps_start = exstart;
			phys_seg_list[i + 1].ps_end = exend;
			physmem += m68k_btop(exend - exstart);
			if (maxmem < m68k_btop(exend))
				maxmem = m68k_btop(exend);
		}
	}
#endif
}

volatile unsigned int intr_depth;

bool
cpu_intr_p(void)
{

	return intr_depth != 0;
}

int
mm_md_physacc(paddr_t pa, vm_prot_t prot)
{
	int rv = mm_md_physacc_regular(pa, prot);

	if (rv != 0) {
		/* I/O space */
		if (INTIOBASE <= pa && pa < INTIOTOP) {
			rv = kauth_authorize_machdep(kauth_cred_get(),
			    KAUTH_MACHDEP_UNMANAGEDMEM, NULL, NULL, NULL, NULL);
		}
	}

	return rv;
}
