#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: arcemu.c,v 1.27 2026/06/11 06:30:55 rumble Exp $");
#ifndef _LP64
#include <sys/param.h>
#include <sys/systm.h>
#include <machine/machtype.h>
#include <sys/bus.h>
#include <dev/cons.h>
#include <dev/arcbios/arcbios.h>
#include <dev/arcbios/arcbiosvar.h>
#include <dev/ic/wd33c93reg.h>
#define _ARCEMU_PRIVATE
#include <sgimips/sgimips/arcemu.h>
#include <sgimips/sgimips/prom.h>
#include <sgimips/dev/picreg.h>
static struct consdev arcemu_cn = {
NULL,
NULL,
NULL,
arcemu_prom_putc,
nullcnpollc,
NULL,
NULL,
NULL,
NODEV,
CN_NORMAL,
};
static struct arcbios_fv arcemu_v = {
.Load = ARCEMU_UNIMPL,
.Invoke = ARCEMU_UNIMPL,
.Execute = ARCEMU_UNIMPL,
.Halt = ARCEMU_UNIMPL,
.PowerDown = ARCEMU_UNIMPL,
.Restart = ARCEMU_UNIMPL,
.Reboot = ARCEMU_UNIMPL,
.EnterInteractiveMode = ARCEMU_UNIMPL,
.ReturnFromMain = 0,
.GetPeer = ARCEMU_UNIMPL,
.GetChild = ARCEMU_UNIMPL,
.GetParent = ARCEMU_UNIMPL,
.GetConfigurationData = ARCEMU_UNIMPL,
.AddChild = ARCEMU_UNIMPL,
.DeleteComponent = ARCEMU_UNIMPL,
.GetComponent = ARCEMU_UNIMPL,
.SaveConfiguration = ARCEMU_UNIMPL,
.GetSystemId = ARCEMU_UNIMPL,
.GetMemoryDescriptor = ARCEMU_UNIMPL,
.Signal = 0,
.GetTime = ARCEMU_UNIMPL,
.GetRelativeTime = ARCEMU_UNIMPL,
.GetDirectoryEntry = ARCEMU_UNIMPL,
.Open = ARCEMU_UNIMPL,
.Close = ARCEMU_UNIMPL,
.Read = ARCEMU_UNIMPL,
.GetReadStatus = ARCEMU_UNIMPL,
.Write = ARCEMU_UNIMPL,
.Seek = ARCEMU_UNIMPL,
.Mount = ARCEMU_UNIMPL,
.GetEnvironmentVariable = ARCEMU_UNIMPL,
.SetEnvironmentVariable = ARCEMU_UNIMPL,
.GetFileInformation = ARCEMU_UNIMPL,
.SetFileInformation = ARCEMU_UNIMPL,
.FlushAllCaches = ARCEMU_UNIMPL
};
int
arcemu_init(const char **env)
{
switch ((mach_type = arcemu_identify())) {
case MACH_SGI_IP6 | MACH_SGI_IP10:
case MACH_SGI_IP12:
arcemu_ipN_init(ARCEMU_ENVOK(env) ? env : NULL);
break;
default:
return (1);
}
ARCBIOS = &arcemu_v;
return (0);
}
static int
arcemu_identify(void)
{
static struct {
const char *pattern;
int mach_type;
} patterns[] = {
{ "IP12/GR2 PROM", MACH_SGI_IP12 },
{ "IP6 Processor", MACH_SGI_IP6 },
{ " PROM IP6 ", MACH_SGI_IP6 },
{ " PROM IP4 ", MACH_SGI_IP4 },
{ " IP 9 ", MACH_SGI_IP9 },
{ " IP 7 ", MACH_SGI_IP7 },
{ " IP 5 ", MACH_SGI_IP5 },
{ NULL, -1 },
};
int mach = -1;
for (int i = 1; i < 3; i++) {
for (int j = 0; patterns[j].pattern != NULL; j++) {
int len = i * 128 * 1024;
if (memmem((void*)MIPS_PHYS_TO_KSEG1(0x1fc00000), len,
patterns[j].pattern,
strlen(patterns[j].pattern)) != NULL) {
mach = patterns[j].mach_type;
break;
}
}
}
if (mach == -1) {
sgi_prom_printf("Failed to identify Pre-ARCBIOS machine. "
"Assuming IP12.\n");
return (MACH_SGI_IP12);
}
sgi_prom_printf("Pre-ARCBIOS machine identified as IP%d\n",
mach);
return (mach);
}
static boolean_t
extractenv(const char **env, const char *key, char *dest, int len)
{
int i;
if (env == NULL)
return (false);
for (i = 0; env[i] != NULL; i++) {
if (strncasecmp(env[i], key, strlen(key)) == 0 &&
env[i][strlen(key)] == '=') {
strlcpy(dest, strchr(env[i], '=') + 1, len);
return (true);
}
}
return (false);
}
static struct arcemu_nvramdata {
char bootmode;
char state;
char netaddr[16];
char lbaud[5];
char rbaud[5];
char console;
char sccolor[6];
char pgcolor[6];
char lgcolor[6];
char keydb;
char pad0;
char checksum;
char diskless;
char nokdb;
char bootfile[50];
char passwd[17];
char volume[3];
uint8_t enaddr[6];
} nvram;
static char enaddr[18];
static struct arcemu_sgienv {
char dbaud[5];
char rbaud[5];
char bootmode;
char console;
char diskless;
char volume[4];
char cpufreq[3];
char gfx[32];
char netaddr[32];
char dlserver[32];
char osloadoptions[32];
} sgienv;
static void
eeprom_read(uint8_t *eeprom_buf, size_t len, int is_cs56,
void (*set_pre)(int), void (*set_cs)(int), void (*set_sk)(int),
int (*get_do)(void), void (*set_di)(int))
{
int i, j;
for (i = 0; i < (len / 2); i++) {
uint16_t instr = 0xc000 | (i << ((is_cs56) ? 5 : 7));
uint16_t bitword = 0;
set_di(0);
set_sk(0);
set_pre(0);
set_cs(0);
set_cs(1);
set_sk(1);
for (j = 0; j < ((is_cs56) ? 11 : 9); j++) {
set_di(instr & 0x8000);
set_sk(0);
set_sk(1);
instr <<= 1;
}
set_di(0);
for (j = 0; j < 17; j++) {
bitword = (bitword << 1) | get_do();
set_sk(0);
set_sk(1);
}
eeprom_buf[i * 2 + 0] = bitword >> 8;
eeprom_buf[i * 2 + 1] = bitword & 0xff;
set_sk(0);
set_cs(0);
}
}
static void
arcemu_eeprom_read(void)
{
int i;
for (i = 1; i >= 0; i--) {
if (mach_type == (MACH_SGI_IP6 | MACH_SGI_IP10)) {
eeprom_read((uint8_t *)&nvram, sizeof(nvram), i,
ip6_set_pre, ip6_set_cs, ip6_set_sk,
ip6_get_do, ip6_set_di);
} else {
eeprom_read((uint8_t *)&nvram, sizeof(nvram), i,
ip12_set_pre, ip12_set_cs, ip12_set_sk,
ip12_get_do, ip12_set_di);
}
if (nvram.enaddr[0] == 0x08 && nvram.enaddr[1] == 0x00 &&
nvram.enaddr[2] == 0x69)
break;
if (memcmp(nvram.lbaud, "9600\x0", 5) == 0)
break;
if (memcmp(nvram.bootfile, "dksc(", 5) == 0 ||
memcmp(nvram.bootfile, "bootp(", 6) == 0)
break;
}
snprintf(enaddr, sizeof(enaddr), "%02x:%02x:%02x:%02x:%02x:%02x",
nvram.enaddr[0],
nvram.enaddr[1],
nvram.enaddr[2],
nvram.enaddr[3],
nvram.enaddr[4],
nvram.enaddr[5]);
}
static void
arcemu_ipN_init(const char **env)
{
arcemu_v.GetPeer = (intptr_t)arcemu_GetPeer;
arcemu_v.GetChild = (intptr_t)arcemu_GetChild;
arcemu_v.GetEnvironmentVariable = (intptr_t)arcemu_GetEnvironmentVariable;
if (mach_type == MACH_SGI_IP6 || mach_type == MACH_SGI_IP10)
arcemu_v.GetMemoryDescriptor = (intptr_t)arcemu_ip6_GetMemoryDescriptor;
else if (mach_type == MACH_SGI_IP12)
arcemu_v.GetMemoryDescriptor = (intptr_t)arcemu_ip12_GetMemoryDescriptor;
arcemu_v.Reboot = (intptr_t)sgi_prom_reset;
arcemu_v.PowerDown = (intptr_t)sgi_prom_reinit;
arcemu_v.EnterInteractiveMode = (intptr_t)sgi_prom_reinit;
cn_tab = &arcemu_cn;
arcemu_eeprom_read();
memset(&sgienv, 0, sizeof(sgienv));
extractenv(env, "dbaud", sgienv.dbaud, sizeof(sgienv.dbaud));
extractenv(env, "rbaud", sgienv.rbaud, sizeof(sgienv.rbaud));
extractenv(env, "bootmode",&sgienv.bootmode, sizeof(sgienv.bootmode));
extractenv(env, "console", &sgienv.console, sizeof(sgienv.console));
extractenv(env, "diskless",&sgienv.diskless, sizeof(sgienv.diskless));
extractenv(env, "volume", sgienv.volume, sizeof(sgienv.volume));
extractenv(env, "cpufreq", sgienv.cpufreq, sizeof(sgienv.cpufreq));
extractenv(env, "gfx", sgienv.gfx, sizeof(sgienv.gfx));
extractenv(env, "netaddr", sgienv.netaddr, sizeof(sgienv.netaddr));
extractenv(env, "dlserver", sgienv.dlserver, sizeof(sgienv.dlserver));
extractenv(env, "osloadoptions", sgienv.osloadoptions,
sizeof(sgienv.osloadoptions));
strcpy(arcbios_sysid_vendor, "SGI");
if (mach_type == MACH_SGI_IP6 || mach_type == MACH_SGI_IP10) {
strcpy(arcbios_system_identifier, "SGI-IP6");
strcpy(arcbios_sysid_product, "IP6");
} else if (mach_type == MACH_SGI_IP12) {
strcpy(arcbios_system_identifier, "SGI-IP12");
strcpy(arcbios_sysid_product, "IP12");
}
}
static void *
arcemu_GetPeer(void *node)
{
int i;
if (node == NULL)
return (NULL);
for (i = 0; arcemu_component_tree[i].Class != -1; i++) {
if (&arcemu_component_tree[i] == node &&
arcemu_component_tree[i+1].Class != -1)
return (&arcemu_component_tree[i+1]);
}
return (NULL);
}
static void *
arcemu_GetChild(void *node)
{
if (node == NULL)
return (&arcemu_component_tree[0]);
return (NULL);
}
static const char *
arcemu_GetEnvironmentVariable(const char *var)
{
if (strcasecmp("ConsoleOut", var) == 0) {
if (strstr(sgienv.gfx, "dead") != NULL)
return "serial(0)";
switch (nvram.console) {
case 'd':
case 'D':
case 's':
case 'S':
return "serial(0)";
case 'g':
case 'G':
return "video()";
default:
printf("arcemu: unknown console \"%c\", using serial\n",
nvram.console);
return "serial(0)";
}
}
if (strcasecmp("cpufreq", var) == 0) {
if (sgienv.cpufreq[0] != '\0')
return (sgienv.cpufreq);
if (mach_type == MACH_SGI_IP6 || mach_type == MACH_SGI_IP10)
return ("16");
return ("33");
}
if (strcasecmp("dbaud", var) == 0)
return (nvram.lbaud);
if (strcasecmp("eaddr", var) == 0)
return (enaddr);
if (strcasecmp("gfx", var) == 0) {
if (sgienv.gfx[0] != '\0')
return (sgienv.gfx);
}
if (strcasecmp("OSLoadPartition", var) == 0) {
char *hack;
hack = strstr(nvram.bootfile, ",8)");
if (hack != NULL)
hack[1] = '0';
return (nvram.bootfile);
}
if (strcasecmp("OSLoadFilename", var) == 0) {
char *file;
if ((file = strrchr(nvram.bootfile, ')')) != NULL)
return (file + 1);
else
return (NULL);
}
if (strcasecmp("OSLoadOptions", var) == 0) {
if (sgienv.osloadoptions[0] == '\0')
return ("auto");
else
return (sgienv.osloadoptions);
}
return (NULL);
}
static void *
arcemu_ip6_GetMemoryDescriptor(void *mem)
{
static struct arcbios_mem am;
static int invoc;
unsigned int pages;
u_int8_t memcfg;
if (mem == NULL) {
am.Type = ARCBIOS_MEM_ExceptionBlock;
am.BasePage = 0;
am.PageCount = 2;
invoc = 0;
return (&am);
}
memcfg = *(volatile uint8_t *)MIPS_PHYS_TO_KSEG1(0x1f800000) & 0x1f;
pages = (memcfg & 0x0f) + 1;
if (memcfg & 0x10) {
pages *= 4096;
#if 0
volatile uint8_t *tp1, *tp2;
uint8_t tmp;
tp1 = (volatile uint8_t *)MIPS_PHYS_TO_KSEG1((pages - 4096) << 12);
tp2 = tp1 + (4 * 1024 * 1024);
tmp = *tp1;
*tp2 = ~tmp;
if (*tp1 != tmp)
pages -= (3 * 1024);
#endif
} else {
pages *= 1024;
}
switch (invoc) {
case 0:
am.BasePage = 2;
am.PageCount = 894;
am.Type = ARCBIOS_MEM_FreeContiguous;
break;
case 1:
am.BasePage = 896;
am.PageCount = 128;
am.Type = ARCBIOS_MEM_FirmwareTemporary;
break;
case 2:
am.BasePage = 1024;
if (pages < 1024)
am.PageCount = 0;
else
am.PageCount = pages - 1024;
am.Type = ARCBIOS_MEM_FreeContiguous;
break;
default:
return (NULL);
}
invoc++;
return (&am);
}
static struct arcbios_mem
arcemu_mem(int type, int base, int count)
{
struct arcbios_mem am = {
.Type = type,
.BasePage = base,
.PageCount = count
};
return am;
}
static void *
arcemu_ip12_GetMemoryDescriptor(void *mem)
{
static int call_num = 0;
static struct arcbios_mem am[8];
static int num_am = 0;
if (num_am == 0) {
u_int32_t memcfg;
for (int bank = 0; bank < 4; bank++) {
switch (bank) {
case 0:
memcfg = *(volatile u_int32_t *)
MIPS_PHYS_TO_KSEG1(PIC_MEMCFG0_PHYSADDR) >> 16;
break;
case 1:
memcfg = *(volatile u_int32_t *)
MIPS_PHYS_TO_KSEG1(PIC_MEMCFG0_PHYSADDR) & 0xffff;
break;
case 2:
memcfg = *(volatile u_int32_t *)
MIPS_PHYS_TO_KSEG1(PIC_MEMCFG1_PHYSADDR) >> 16;
break;
case 3:
memcfg = *(volatile u_int32_t *)
MIPS_PHYS_TO_KSEG1(PIC_MEMCFG1_PHYSADDR) & 0xffff;
break;
}
const int pgsize = ARCBIOS_PAGESIZE;
int bp = PIC_MEMCFG_ADDR(memcfg) / pgsize;
int pc = PIC_MEMCFG_SIZ(memcfg) / pgsize;
int ep = bp + pc;
const int BadMemory = ARCBIOS_MEM_BadMemory;
const int FreeMemory = ARCBIOS_MEM_FreeContiguous;
const int ExceptionBlock = ARCBIOS_MEM_ExceptionBlock;
const int PromMemory = ARCBIOS_MEM_FirmwareTemporary;
if (memcfg == PIC_MEMCFG_BADADDR) {
sgi_prom_printf("Bank %d: No RAM present\n", bank);
am[num_am++] = arcemu_mem(BadMemory, bp, 0);
} else if (bp >= 1024) {
sgi_prom_printf("Bank %d base 0x%x pages %d\n",
bank, bp, pc);
am[num_am++] = arcemu_mem(FreeMemory, bp, pc);
} else {
sgi_prom_printf("Bank %d base 0x%x pages %d",
bank, bp, pc);
int reserved = 0;
if (bp == 0) {
am[num_am++] = arcemu_mem(
ExceptionBlock, 0, 2);
bp += 2;
pc -= 2;
reserved += 2;
}
if (bp < 896 && ep >= 1024) {
am[num_am++] = arcemu_mem(
FreeMemory, bp, 896 - bp);
am[num_am++] = arcemu_mem(
PromMemory, 896, 128);
am[num_am++] = arcemu_mem(
FreeMemory, 1024, ep - 1024);
reserved += 128;
} else {
am[num_am++] = arcemu_mem(
FreeMemory, bp, pc);
}
if (reserved > 0) {
sgi_prom_printf(" (%d reserved)",
reserved);
}
sgi_prom_printf("\n");
}
}
}
return ((call_num < num_am) ? &am[call_num++] : NULL);
}
static void
arcemu_prom_putc(dev_t dummy, int c)
{
sgi_prom_printf("%c", c);
}
static void
arcemu_unimpl(void)
{
panic("arcemu vector not established on IP%d", mach_type);
}
#endif