338 lines
9.3 KiB
C
338 lines
9.3 KiB
C
|
/* Initialization of the system and the HWRPB.
|
||
|
|
||
|
Copyright (C) 2011 Richard Henderson
|
||
|
|
||
|
This file is part of QEMU PALcode.
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation; either version 2 of the License or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the text
|
||
|
of the GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; see the file COPYING. If not see
|
||
|
<http://www.gnu.org/licenses/>. */
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <stddef.h>
|
||
|
#include "hwrpb.h"
|
||
|
#include "osf.h"
|
||
|
#include "ioport.h"
|
||
|
#include "uart.h"
|
||
|
#include "protos.h"
|
||
|
#include SYSTEM_H
|
||
|
|
||
|
#define PAGE_SHIFT 13
|
||
|
#define PAGE_SIZE (1ul << PAGE_SHIFT)
|
||
|
#define PAGE_OFFSET 0xfffffc0000000000UL
|
||
|
|
||
|
#define VPTPTR 0xfffffffe00000000UL
|
||
|
|
||
|
#define PA(VA) ((unsigned long)(VA) & 0xfffffffffful)
|
||
|
#define VA(PA) ((void *)(PA) + PAGE_OFFSET)
|
||
|
|
||
|
#define HZ 1024
|
||
|
|
||
|
struct hwrpb_combine {
|
||
|
struct hwrpb_struct hwrpb;
|
||
|
struct percpu_struct processor[4];
|
||
|
struct memdesc_struct md;
|
||
|
struct memclust_struct mc[2];
|
||
|
struct crb_struct crb;
|
||
|
struct procdesc_struct proc_dispatch;
|
||
|
struct procdesc_struct proc_fixup;
|
||
|
};
|
||
|
|
||
|
extern char stack[PAGE_SIZE] __attribute__((section(".sbss")));
|
||
|
extern char _end[] __attribute__((visibility("hidden"), nocommon));
|
||
|
|
||
|
struct pcb_struct pcb __attribute__((section(".sbss")));
|
||
|
|
||
|
static unsigned long page_dir[1024]
|
||
|
__attribute__((aligned(PAGE_SIZE), section(".bss.page_dir")));
|
||
|
|
||
|
/* The HWRPB must be aligned because it is exported at INIT_HWRPB. */
|
||
|
struct hwrpb_combine hwrpb __attribute__((aligned(PAGE_SIZE)));
|
||
|
|
||
|
void *last_alloc;
|
||
|
bool have_vga;
|
||
|
|
||
|
static void *
|
||
|
alloc (unsigned long size, unsigned long align)
|
||
|
{
|
||
|
void *p = (void *)(((unsigned long)last_alloc + align - 1) & ~(align - 1));
|
||
|
last_alloc = p + size;
|
||
|
return memset (p, 0, size);
|
||
|
}
|
||
|
|
||
|
static inline unsigned long
|
||
|
pt_index(unsigned long addr, int level)
|
||
|
{
|
||
|
return (addr >> (PAGE_SHIFT + (10 * level))) & 0x3ff;
|
||
|
}
|
||
|
|
||
|
static inline unsigned long
|
||
|
build_pte (void *page)
|
||
|
{
|
||
|
unsigned long bits;
|
||
|
|
||
|
bits = PA((unsigned long)page) << (32 - PAGE_SHIFT);
|
||
|
bits += _PAGE_VALID | _PAGE_KRE | _PAGE_KWE;
|
||
|
|
||
|
return bits;
|
||
|
}
|
||
|
|
||
|
static inline void *
|
||
|
pte_page (unsigned long pte)
|
||
|
{
|
||
|
return VA(pte >> 32 << PAGE_SHIFT);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
set_pte (unsigned long addr, void *page)
|
||
|
{
|
||
|
unsigned long *pt = page_dir;
|
||
|
unsigned long index;
|
||
|
|
||
|
index = pt_index(addr, 2);
|
||
|
if (pt[index] != 0)
|
||
|
pt = pte_page (pt[index]);
|
||
|
else
|
||
|
{
|
||
|
unsigned long *npt = alloc(PAGE_SIZE, PAGE_SIZE);
|
||
|
pt[index] = build_pte (npt);
|
||
|
pt = npt;
|
||
|
}
|
||
|
|
||
|
index = pt_index(addr, 1);
|
||
|
if (pt[index] != 0)
|
||
|
pt = pte_page (pt[index]);
|
||
|
else
|
||
|
{
|
||
|
unsigned long *npt = alloc(PAGE_SIZE, PAGE_SIZE);
|
||
|
pt[index] = build_pte (npt);
|
||
|
pt = npt;
|
||
|
}
|
||
|
|
||
|
index = pt_index(addr, 0);
|
||
|
pt[index] = build_pte (page);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
init_page_table(void)
|
||
|
{
|
||
|
/* Install the self-reference for the virtual page table base register. */
|
||
|
page_dir[pt_index(VPTPTR, 2)] = build_pte(page_dir);
|
||
|
|
||
|
set_pte ((unsigned long)INIT_HWRPB, &hwrpb);
|
||
|
|
||
|
/* ??? SRM maps some amount of memory at 0x20000000 for use by programs
|
||
|
started from the console prompt. Including the bootloader. While
|
||
|
we're emulating MILO, don't bother as we jump straight to the kernel
|
||
|
loaded into KSEG. */
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
init_hwrpb (unsigned long memsize, unsigned long cpus)
|
||
|
{
|
||
|
unsigned long pal_pages;
|
||
|
unsigned long amask;
|
||
|
unsigned long i;
|
||
|
|
||
|
hwrpb.hwrpb.phys_addr = PA(&hwrpb);
|
||
|
|
||
|
/* Yes, the 'HWRPB' magic is in big-endian byte ordering. */
|
||
|
hwrpb.hwrpb.id = ( (long)'H' << 56
|
||
|
| (long)'W' << 48
|
||
|
| (long)'R' << 40
|
||
|
| (long)'P' << 32
|
||
|
| (long)'B' << 24);
|
||
|
|
||
|
hwrpb.hwrpb.size = sizeof(struct hwrpb_struct);
|
||
|
|
||
|
((int *)hwrpb.hwrpb.ssn)[0] = ( 'Q' << 0
|
||
|
| 'E' << 8
|
||
|
| 'M' << 16
|
||
|
| 'U' << 24);
|
||
|
|
||
|
amask = ~__builtin_alpha_amask(-1);
|
||
|
switch (__builtin_alpha_implver())
|
||
|
{
|
||
|
case 0: /* EV4 */
|
||
|
hwrpb.hwrpb.cpuid = EV4_CPU;
|
||
|
hwrpb.hwrpb.max_asn = 63;
|
||
|
break;
|
||
|
|
||
|
case 1: /* EV5 */
|
||
|
hwrpb.hwrpb.cpuid
|
||
|
= ((amask & 0x101) == 0x101 ? PCA56_CPU /* MAX+BWX */
|
||
|
: amask & 1 ? EV56_CPU /* BWX */
|
||
|
: EV5_CPU);
|
||
|
hwrpb.hwrpb.max_asn = 127;
|
||
|
break;
|
||
|
|
||
|
case 2: /* EV6 */
|
||
|
hwrpb.hwrpb.cpuid = (amask & 4 ? EV67_CPU : EV6_CPU); /* CIX */
|
||
|
hwrpb.hwrpb.max_asn = 255;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
hwrpb.hwrpb.pagesize = PAGE_SIZE;
|
||
|
hwrpb.hwrpb.pa_bits = 40;
|
||
|
hwrpb.hwrpb.sys_type = SYS_TYPE;
|
||
|
hwrpb.hwrpb.sys_variation = SYS_VARIATION;
|
||
|
hwrpb.hwrpb.sys_revision = SYS_REVISION;
|
||
|
for (i = 0; i < cpus; ++i)
|
||
|
{
|
||
|
/* ??? Look up these bits. Snagging the value examined by the kernel. */
|
||
|
hwrpb.processor[i].flags = 0x1cc;
|
||
|
hwrpb.processor[i].type = hwrpb.hwrpb.cpuid;
|
||
|
}
|
||
|
|
||
|
hwrpb.hwrpb.intr_freq = HZ * 4096;
|
||
|
hwrpb.hwrpb.cycle_freq = 250000000; /* QEMU architects 250MHz. */
|
||
|
|
||
|
hwrpb.hwrpb.vptb = VPTPTR;
|
||
|
|
||
|
hwrpb.hwrpb.nr_processors = cpus;
|
||
|
hwrpb.hwrpb.processor_size = sizeof(struct percpu_struct);
|
||
|
hwrpb.hwrpb.processor_offset = offsetof(struct hwrpb_combine, processor);
|
||
|
|
||
|
hwrpb.hwrpb.mddt_offset = offsetof(struct hwrpb_combine, md);
|
||
|
hwrpb.md.numclusters = 2;
|
||
|
|
||
|
pal_pages = (PA(last_alloc) + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
||
|
|
||
|
hwrpb.mc[0].numpages = pal_pages;
|
||
|
hwrpb.mc[0].usage = 1;
|
||
|
hwrpb.mc[1].start_pfn = pal_pages;
|
||
|
hwrpb.mc[1].numpages = (memsize >> PAGE_SHIFT) - pal_pages;
|
||
|
|
||
|
hwrpb.hwrpb.crb_offset = offsetof(struct hwrpb_combine, crb);
|
||
|
hwrpb.crb.dispatch_va = &hwrpb.proc_dispatch;
|
||
|
hwrpb.crb.dispatch_pa = PA(&hwrpb.proc_dispatch);
|
||
|
hwrpb.crb.fixup_va = &hwrpb.proc_fixup;
|
||
|
hwrpb.crb.fixup_pa = PA(&hwrpb.proc_fixup);
|
||
|
hwrpb.crb.map_entries = 1;
|
||
|
hwrpb.crb.map_pages = 1;
|
||
|
hwrpb.crb.map[0].va = &hwrpb;
|
||
|
hwrpb.crb.map[0].pa = PA(&hwrpb);
|
||
|
hwrpb.crb.map[0].count = 1;
|
||
|
|
||
|
/* See crb.c for how we match the VMS calling conventions to Unix. */
|
||
|
hwrpb.proc_dispatch.address = (unsigned long)crb_dispatch;
|
||
|
hwrpb.proc_fixup.address = (unsigned long)crb_fixup;
|
||
|
|
||
|
hwrpb_update_checksum(&hwrpb.hwrpb);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
init_pcb (void)
|
||
|
{
|
||
|
pcb.ksp = (unsigned long)stack + sizeof(stack);
|
||
|
pcb.ptbr = PA(page_dir) >> PAGE_SHIFT;
|
||
|
pcb.flags = 1; /* FEN */
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
init_i8259 (void)
|
||
|
{
|
||
|
/* ??? MILO initializes the PIC as edge triggered; I do not know how SRM
|
||
|
initializes them. However, Linux seems to expect that these are level
|
||
|
triggered. That may be a kernel bug, but level triggers are more
|
||
|
reliable anyway so lets go with that. */
|
||
|
|
||
|
/* Initialize the slave PIC. */
|
||
|
outb(0x11, PORT_PIC2_CMD); /* ICW1: edge trigger, cascade, ICW4 req */
|
||
|
outb(0x08, PORT_PIC2_DATA); /* ICW2: irq offset = 8 */
|
||
|
outb(0x02, PORT_PIC2_DATA); /* ICW3: slave ID 2 */
|
||
|
outb(0x01, PORT_PIC2_DATA); /* ICW4: not special nested, normal eoi */
|
||
|
|
||
|
/* Initialize the master PIC. */
|
||
|
outb(0x11, PORT_PIC1_CMD); /* ICW1 */
|
||
|
outb(0x00, PORT_PIC1_DATA); /* ICW2: irq offset = 0 */
|
||
|
outb(0x04, PORT_PIC1_DATA); /* ICW3: slave control INTC2 */
|
||
|
outb(0x01, PORT_PIC1_DATA); /* ICW4 */
|
||
|
|
||
|
/* Initialize level triggers. The CY82C693UB that's on real alpha
|
||
|
hardware doesn't have this; this is a PIIX extension. However,
|
||
|
QEMU doesn't implement regular level triggers. */
|
||
|
outb(0xff, PORT_PIC2_ELCR);
|
||
|
outb(0xff, PORT_PIC1_ELCR);
|
||
|
|
||
|
/* Disable all interrupts. */
|
||
|
outb(0xff, PORT_PIC2_DATA);
|
||
|
outb(0xff, PORT_PIC1_DATA);
|
||
|
|
||
|
/* Non-specific EOI, clearing anything the might be pending. */
|
||
|
outb(0x20, PORT_PIC2_CMD);
|
||
|
outb(0x20, PORT_PIC1_CMD);
|
||
|
}
|
||
|
|
||
|
static void __attribute__((noreturn))
|
||
|
swppal(void *entry, void *pcb)
|
||
|
{
|
||
|
register int variant __asm__("$16") = 2; /* OSF/1 PALcode */
|
||
|
register void *pc __asm__("$17") = entry;
|
||
|
register unsigned long pa_pcb __asm__("$18") = PA(pcb);
|
||
|
register unsigned long vptptr __asm__("$19") = VPTPTR;
|
||
|
|
||
|
asm("call_pal 0x0a" : : "r"(variant), "r"(pc), "r"(pa_pcb), "r"(vptptr));
|
||
|
__builtin_unreachable ();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
do_start(unsigned long memsize, void (*kernel_entry)(void), unsigned long cpus)
|
||
|
{
|
||
|
last_alloc = _end;
|
||
|
|
||
|
init_page_table();
|
||
|
init_hwrpb(memsize, cpus);
|
||
|
init_pcb();
|
||
|
init_i8259();
|
||
|
uart_init();
|
||
|
ps2port_setup();
|
||
|
pci_setup();
|
||
|
vgahw_init();
|
||
|
|
||
|
swppal(kernel_entry ? kernel_entry : do_console, &pcb);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
do_start_wait(unsigned long cpuid)
|
||
|
{
|
||
|
while (1)
|
||
|
{
|
||
|
/* Wait 1ms for the kernel to wake us. */
|
||
|
ndelay(1000000);
|
||
|
|
||
|
if (hwrpb.hwrpb.rxrdy & (1ull << cpuid))
|
||
|
{
|
||
|
/* ??? The only message I know of is "START\r\n".
|
||
|
I can't be bothered to verify more than 4 characters. */
|
||
|
/* ??? The Linux kernel fills in, but does not require,
|
||
|
CPU_restart_data. It just sets that to the same address
|
||
|
as CPU_restart itself. Our swppal *does* put the PC into
|
||
|
$26 and $27, the latter of which the kernel does rely upon. */
|
||
|
|
||
|
unsigned int len = hwrpb.processor[cpuid].ipc_buffer[0];
|
||
|
unsigned int msg = hwrpb.processor[cpuid].ipc_buffer[1];
|
||
|
void *CPU_restart = hwrpb.hwrpb.CPU_restart;
|
||
|
__sync_synchronize();
|
||
|
hwrpb.hwrpb.rxrdy = 0;
|
||
|
|
||
|
if (len == 7 && msg == ('S' | 'T' << 8 | 'A' << 16 | 'R' << 24))
|
||
|
{
|
||
|
/* Set bootstrap in progress */
|
||
|
hwrpb.processor[cpuid].flags |= 1;
|
||
|
swppal(CPU_restart, hwrpb.processor[cpuid].hwpcb);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|