root/sys/arch/amigappc/amigappc/machdep.c
/* $NetBSD: machdep.c,v 1.56 2025/12/20 10:51:00 skrll Exp $ */

/*
 * Copyright (C) 1995, 1996 Wolfgang Solfrank.
 * Copyright (C) 1995, 1996 TooLs GmbH.
 * All rights reserved.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by TooLs GmbH.
 * 4. The name of TooLs GmbH may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: machdep.c,v 1.56 2025/12/20 10:51:00 skrll Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/mount.h>
#include <sys/msgbuf.h>
#include <sys/kernel.h>
#include <sys/reboot.h>
#include <sys/ksyms.h>

#include <uvm/uvm_extern.h>

#include <dev/cons.h>

#include <machine/autoconf.h>
#include <machine/powerpc.h>

#include <powerpc/oea/bat.h>
#include <powerpc/pic/picvar.h>

#include <amiga/amiga/cc.h>
#include <amiga/amiga/cia.h>
#include <amiga/amiga/custom.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/isr.h>
#include <amiga/amiga/memlist.h>
#include <amigappc/amigappc/p5reg.h>

#include "opt_ddb.h"

#include "fd.h"
#include "ser.h"

extern void setup_amiga_intr(void);
#if NSER > 0
extern void ser_outintr(void);
extern void ser_fastint(void);
#endif
#if NFD > 0
extern void fdintr(int);
#endif

#define AMIGAMEMREGIONS 8
static struct mem_region physmemr[AMIGAMEMREGIONS], availmemr[AMIGAMEMREGIONS];
static char model[80];

/*
 * patched by some devices at attach time (currently, only the coms)
 */
int amiga_serialspl = 4;

/*
 * current open serial device speed;  used by some SCSI drivers to reduce
 * DMA transfer lengths.
 */
int ser_open_speed;

/* interrupt handler chains for level2 and level6 interrupt */
struct isr *isr_ports;
struct isr *isr_exter;

extern void *startsym, *endsym;

void
add_isr(struct isr *isr)
{
        struct isr **p, *q;

        p = isr->isr_ipl == 2 ? &isr_ports : &isr_exter;

        while ((q = *p) != NULL)
                p = &q->isr_forw;
        isr->isr_forw = NULL;
        *p = isr;

        /* enable interrupt */
        custom.intena = isr->isr_ipl == 2 ?
            INTF_SETCLR | INTF_PORTS :
            INTF_SETCLR | INTF_EXTER;
}

void
remove_isr(struct isr *isr)
{
        struct isr **p, *q;

        p = isr->isr_ipl == 6 ? &isr_exter : &isr_ports;

        while ((q = *p) != NULL && q != isr)
                p = &q->isr_forw;
        if (q)
                *p = q->isr_forw;
        else
                panic("remove_isr: handler not registered");

        /* disable interrupt if no more handlers */
        p = isr->isr_ipl == 6 ? &isr_exter : &isr_ports;
        if (*p == NULL) {
                custom.intena = isr->isr_ipl == 6 ?
                    INTF_EXTER : INTF_PORTS;
        }
}

static int
ports_intr(void *arg)
{
        struct isr **p;
        struct isr *q;

        p = (struct isr **)arg;
        while ((q = *p) != NULL) {
                if ((q->isr_intr)(q->isr_arg))
                        break;
                p = &q->isr_forw;
        }
        if (q == NULL)
                ciaa_intr();  /* ciaa handles keyboard and parallel port */

        custom.intreq = INTF_PORTS;
        return 0;
}

static int
exter_intr(void *arg)
{
        struct isr **p;
        struct isr *q;

        p = (struct isr **)arg;
        while ((q = *p) != NULL) {
                if ((q->isr_intr)(q->isr_arg))
                        break;
                p = &q->isr_forw;
        }
        if (q == NULL)
                ciab_intr();  /* clear ciab icr */

        custom.intreq = INTF_EXTER;
        return 0;
}

static int
lev1_intr(void *arg)
{
        unsigned short ireq;

        ireq = custom.intreqr;
        if (ireq & INTF_TBE) {
#if NSER > 0
                ser_outintr();
#else
                custom.intreq = INTF_TBE;
#endif
        }
        if (ireq & INTF_DSKBLK) {
#if NFD > 0
                fdintr(0);
#endif
                custom.intreq = INTF_DSKBLK;
        }
        if (ireq & INTF_SOFTINT) {
#ifdef DEBUG
                printf("intrhand: SOFTINT ignored\n");
#endif
                custom.intreq = INTF_SOFTINT;
        }
        return 0;
}

static int
lev3_intr(void *arg)
{
        unsigned short ireq;

        ireq = custom.intreqr;
        if (ireq & INTF_BLIT)
                blitter_handler();
        if (ireq & INTF_COPER)
                copper_handler();
        if (ireq & INTF_VERTB)
                vbl_handler();
        return 0;
}

static int
lev4_intr(void *arg)
{

        audio_handler();
        return 0;
}

static int
lev5_intr(void *arg)
{

        ser_fastint();
        if (custom.intreqr & INTF_DSKSYNC)
                custom.intreq = INTF_DSKSYNC;
        return 0;
}

static void
amigappc_install_handlers(void)
{

        /* handlers for all 6 Amiga interrupt levels */
        intr_establish(1, IST_LEVEL, IPL_BIO, lev1_intr, NULL);
        intr_establish(2, IST_LEVEL, IPL_BIO, ports_intr, &isr_ports);
        intr_establish(3, IST_LEVEL, IPL_TTY, lev3_intr, NULL);
        intr_establish(4, IST_LEVEL, IPL_AUDIO, lev4_intr, NULL);
        intr_establish(5, IST_LEVEL, IPL_SERIAL, lev5_intr, NULL);
        intr_establish(6, IST_LEVEL, IPL_SERIAL, exter_intr, &isr_exter);
}

static void
amigappc_identify(void)
{
        extern u_long ns_per_tick, ticks_per_sec;
        static const unsigned char pll[] = {
                10, 10, 70, 10, 20, 65, 25, 45,
                30, 55, 40, 50, 15, 60, 35, 00
        };
        const char *cpuname, *mach, *p5type_p, *pup;
        u_long busclock, cpuclock;
        register int pvr, hid1;

        /* PowerUp ROM id location */
        p5type_p = (const char *)0xf00010;

        /*
         * PVR holds the CPU-type and version while
         * HID1 holds the PLL configuration
         */
        __asm ("mfpvr %0; mfspr %1,1009" : "=r"(pvr), "=r"(hid1));

        /* Amiga types which can run a PPC board */
        if (is_a4000())
                mach = "Amiga 4000";
        else if (is_a3000())
                mach = "Amiga 3000";
        else
                mach = "Amiga 1200";

        /* find CPU type - BlizzardPPC has 603e/603ev, CyberstormPPC has 604e */
        switch (pvr >> 16) {
        case 3:
                cpuname = "603";
                break;
        case 4:
                cpuname = "604";
                break;
        case 6:
                cpuname = "603e";
                break;
        case 7:
                cpuname = "603ev";
                break;
        case 9:
        case 10:
                cpuname = "604e";
                break;
        default:
                cpuname = "unknown";
                break;
        }

        switch (p5type_p[0]) {
        case 'D':
                pup = "[PowerUP]";
                break;
        case 'E':
                pup = "[CSPPC]";
                break;
        case 'F':
                pup = "[CS Mk.III]";
                break;
        case 'H':
        case 'I':
                pup = "[BlizzardPPC]";
                break;
        default:
                pup = "";
                break;
        }

        /* XXX busclock can be measured with CIA-timers */
        switch (p5type_p[1]) {
        case 'A':
                busclock = 60000000;
                break;
        /* case B, C, D */
        default:
                busclock = 66666667;
                break;
        }

        /* compute cpuclock based on PLL configuration */
        cpuclock = busclock * pll[hid1>>28 & 0xf] / 10;

        snprintf(model, sizeof(model),
            "%s %s (%s v%d.%d %lu MHz, busclk %lu MHz)",
            mach, pup, cpuname, (pvr>>8) & 0xf, (pvr >> 0) & 0xf,
            cpuclock / 1000000, busclock / 1000000);

        /* set timebase */
        ticks_per_sec = busclock / 4;
        ns_per_tick = 1000000000 / ticks_per_sec;
}

static void
amigappc_reboot(void)
{

        /* reboot CSPPC/BPPC */
        if (!is_a1200()) {
                P5write(P5_REG_LOCK,0x60);
                P5write(P5_REG_LOCK,0x50);
                P5write(P5_REG_LOCK,0x30);
                P5write(P5_REG_SHADOW,P5_SET_CLEAR|P5_SHADOW);
                P5write(P5_REG_LOCK,0x00);
        } else
                P5write(P5_BPPC_MAGIC,0x00);

        P5write(P5_REG_LOCK,0x60);
        P5write(P5_REG_LOCK,0x50);
        P5write(P5_REG_LOCK,0x30);
        P5write(P5_REG_SHADOW,P5_SELF_RESET);
        P5write(P5_REG_RESET,P5_AMIGA_RESET);
}

static void
amigappc_bat_add(paddr_t pa, register_t len, register_t prot)
{
        static int nd = 0, ni = 0;
        const uint32_t i = pa >> 28;

        battable[i].batl = BATL(pa, prot, BAT_PP_RW);
        battable[i].batu = BATU(pa, len, BAT_Vs);

        /*
         * Let's start loading the BAT registers.
         */
        if (!(prot & (BAT_I | BAT_G))) {
                switch (ni) {
                case 0:
                        __asm volatile ("isync");
                        __asm volatile ("mtibatl 0,%0; mtibatu 0,%1;"
                            ::  "r"(battable[i].batl),
                                "r"(battable[i].batu));
                        __asm volatile ("isync");
                        ni = 1;
                        break;
                case 1:
                        __asm volatile ("isync");
                        __asm volatile ("mtibatl 1,%0; mtibatu 1,%1;"
                            ::  "r"(battable[i].batl),
                                "r"(battable[i].batu));
                        __asm volatile ("isync");
                        ni = 2;
                        break;
                case 2:
                        __asm volatile ("isync");
                        __asm volatile ("mtibatl 2,%0; mtibatu 2,%1;"
                            ::  "r"(battable[i].batl),
                                "r"(battable[i].batu));
                        __asm volatile ("isync");
                        ni = 3;
                        break;
                case 3:
                        __asm volatile ("isync");
                        __asm volatile ("mtibatl 3,%0; mtibatu 3,%1;"
                            ::  "r"(battable[i].batl),
                                "r"(battable[i].batu));
                        __asm volatile ("isync");
                        ni = 4;
                        break;
                default:
                        break;
                }
        }
        switch (nd) {
        case 0:
                __asm volatile ("isync");
                __asm volatile ("mtdbatl 0,%0; mtdbatu 0,%1;"
                    ::  "r"(battable[i].batl),
                        "r"(battable[i].batu));
                __asm volatile ("isync");
                nd = 1;
                break;
        case 1:
                __asm volatile ("isync");
                __asm volatile ("mtdbatl 1,%0; mtdbatu 1,%1;"
                    ::  "r"(battable[i].batl),
                        "r"(battable[i].batu));
                __asm volatile ("isync");
                nd = 2;
                break;
        case 2:
                __asm volatile ("isync");
                __asm volatile ("mtdbatl 2,%0; mtdbatu 2,%1;"
                    ::  "r"(battable[i].batl),
                        "r"(battable[i].batu));
                __asm volatile ("isync");
                nd = 3;
                break;
        case 3:
                __asm volatile ("isync");
                __asm volatile ("mtdbatl 3,%0; mtdbatu 3,%1;"
                    ::  "r"(battable[i].batl),
                        "r"(battable[i].batu));
                __asm volatile ("isync");
                nd = 4;
                break;
        default:
                break;
        }
}

static void
amigappc_batinit(paddr_t pa, ...)
{
        va_list ap;
        register_t msr;

        msr = mfmsr();

        /*
         * Set BAT registers to unmapped to avoid overlapping mappings below.
         */
        if ((msr & (PSL_IR|PSL_DR)) == 0) {
                __asm volatile ("isync");
                __asm volatile ("mtibatu 0,%0" :: "r"(0));
                __asm volatile ("mtibatu 1,%0" :: "r"(0));
                __asm volatile ("mtibatu 2,%0" :: "r"(0));
                __asm volatile ("mtibatu 3,%0" :: "r"(0));
                __asm volatile ("mtdbatu 0,%0" :: "r"(0));
                __asm volatile ("mtdbatu 1,%0" :: "r"(0));
                __asm volatile ("mtdbatu 2,%0" :: "r"(0));
                __asm volatile ("mtdbatu 3,%0" :: "r"(0));
                __asm volatile ("isync");
        }

        /*
         * Setup BATs
         */
        va_start(ap, pa);
        while (pa != ~0) {
                register_t len, prot;

                len = va_arg(ap, register_t);
                prot = va_arg(ap, register_t);
                amigappc_bat_add(pa, len, prot);
                pa = va_arg(ap, paddr_t);
        }
        va_end(ap);
}

void
initppc(u_int startkernel, u_int endkernel)
{
        struct boot_memseg *ms;
        u_int i, r;

        /*
         * amigappc memory region set
         */
        ms = &memlist->m_seg[0];
        for (i = 0, r = 0; i < memlist->m_nseg; i++, ms++) {
                if (ms->ms_attrib & MEMF_FAST) {
                        /*
                         * XXX Only recognize the memory segment in which
                         * the kernel resides, for now
                         */
                        if (ms->ms_start <= startkernel &&
                            ms->ms_start + ms->ms_size > endkernel) {
                                physmemr[r].start = (paddr_t)ms->ms_start;
                                physmemr[r].size = (psize_t)ms->ms_size & ~PGOFSET;
                                availmemr[r].start = (endkernel + PGOFSET) & ~PGOFSET;
                                availmemr[r].size  = (physmemr[0].start +
                                                      physmemr[0].size)
                                                     - availmemr[0].start;
                        }
                        r++;
                }
        }
        physmemr[r].start = 0;
        physmemr[r].size = 0;
        availmemr[r].start = 0;
        availmemr[r].size = 0;
        if (r == 0)
                panic("initppc: no suitable memory segment found");

        /*
         * Initialize BAT tables.
         * The CSPPC RAM (A3000/A4000) always starts at 0x08000000 and is
         * up to 128MB big.
         * The BPPC RAM (A1200) can be up to 256MB and may start at nearly
         * any address between 0x40000000 and 0x80000000 depending on which
         * RAM module of which size was inserted into which bank:
         * The RAM module in bank 1 is located from 0x?8000000 downwards.
         * The RAM module in bank 2 is located from 0x?8000000 upwards.
         * Whether '?' is 4, 5, 6 or 7 probably depends on the size.
         * So we have to use the 'startkernel' symbol for BAT-mapping
         * our RAM.
         */
        if (is_a1200()) {
                amigappc_batinit(0x00000000, BAT_BL_16M, BAT_I|BAT_G,
                    (startkernel & 0xf0000000), BAT_BL_256M, 0,
                    0xfff00000, BAT_BL_512K, 0,
                    ~0);
        } else {
                /* A3000 or A4000 */
                amigappc_batinit(0x00000000, BAT_BL_16M, BAT_I|BAT_G,
                    (startkernel & 0xf8000000), BAT_BL_128M, 0,
                    0xfff00000, BAT_BL_512K, 0,
                    0x40000000, BAT_BL_256M, BAT_I|BAT_G,
                    ~0);
        }

        /*
         * Set up trap vectors and interrupt handler
         */
        oea_init(NULL);

        /* XXX bus_space_init() not needed here */

        uvm_md_init();

        /*
         * Initialize pmap module
         */
        pmap_bootstrap(startkernel, endkernel);

#if NKSYMS || defined(DDB) || defined(MODULAR)
        ksyms_addsyms_elf((int)((u_int)endsym - (u_int)startsym), startsym, endsym);
#endif

        /*
         * CPU model, bus clock, timebase
         */
        amigappc_identify();

#ifdef DDB
        if (boothowto & RB_KDB)
                Debugger();
#endif
}

/*
 * This is called during initppc, before the system is really initialized.
 * It shall provide the total and the available regions of RAM.
 * Both lists must have a zero-size entry as terminator.
 * The available regions need not take the kernel into account, but needs
 * to provide space for two additional entry beyond the terminating one.
 */
void
mem_regions(struct mem_region **memp, struct mem_region **availp)
{

        *memp = physmemr;
        *availp = availmemr;
}

/*
 * Machine dependent startup code
 */
void
cpu_startup(void)
{
        int msr;

        /*
         * hello world
         */
        oea_startup(model);

        /* Setup interrupts */
        pic_init();
        setup_amiga_intr();
        amigappc_install_handlers();

        oea_install_extint(pic_ext_intr);

        /*
         * Now allow hardware interrupts.
         */
        splhigh();
        __asm volatile ("mfmsr %0; ori %0,%0,%1; mtmsr %0"
            :   "=r"(msr)
            :   "K"(PSL_EE));

#if 0 /* XXX Not in amiga bus.h - fix it? */
        /*
         * Now that we have VM, malloc's are OK in bus_space.
         */
        bus_space_mallocok();
#endif
}

/*
 * consinit
 * Initialize system console.
 */
void
consinit(void)
{

        /* preconfigure graphics cards */
        custom_chips_init();
        config_console();

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

/*
 * Halt or reboot the machine after syncing/dumping according to howto
 */
void
cpu_reboot(int howto, char *what)
{
        static int syncing;

        if (cold) {
                howto |= RB_HALT;
                goto halt_sys;
        }

        boothowto = howto;
        if ((howto & RB_NOSYNC) == 0 && syncing == 0) {
                syncing = 1;
                vfs_shutdown();         /* sync */
        }

        /* Disable intr */
        splhigh();

        /* Do dump if requested */
        if ((howto & (RB_DUMP | RB_HALT)) == RB_DUMP) {
                oea_dumpsys();
#ifdef DDB
                /* XXX dumpsys doesn't work, so give a chance to debug */
                Debugger();
#endif
        }

halt_sys:
        doshutdownhooks();

        if (howto & RB_HALT) {
                printf("\n");
                printf("The operating system has halted.\n");
                printf("Please press any key to reboot.\n\n");
                cnpollc(true);  /* for proper keyboard command handling */
                cngetc();
                cnpollc(false);
        }

        printf("rebooting...\n\n");
        delay(1000000);
        amigappc_reboot();

        for (;;);
        /* NOTREACHED */
}

/*
 * Try to emulate the functionality from m68k/m68k/sys_machdep.c
 * used by several amiga scsi drivers.
 */
int
dma_cachectl(void *addr, int len)
{
        paddr_t pa, end;
        int inc;

        if (addr == NULL || len == 0)
                return 0;

        pa = kvtop(addr);
        inc = curcpu()->ci_ci.dcache_line_size;

        for (end = pa + len; pa < end; pa += inc)
                __asm volatile("dcbf 0,%0" :: "r"(pa));
        __asm volatile("sync");

#if 0 /* XXX not needed, we don't have instructions in DMA buffers */
        pa = kvtop(addr);
        for (end = pa + len; pa < end; pa += inc)
                __asm volatile("icbi 0,%0" :: "r"(pa));
        __asm volatile("isync");
#endif

        return 0;
}