diff options
author | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
---|---|---|
committer | Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> | 2023-10-10 11:40:56 +0000 |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/alpha | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/alpha')
-rw-r--r-- | hw/alpha/Kconfig | 11 | ||||
-rw-r--r-- | hw/alpha/alpha_sys.h | 21 | ||||
-rw-r--r-- | hw/alpha/dp264.c | 222 | ||||
-rw-r--r-- | hw/alpha/meson.build | 8 | ||||
-rw-r--r-- | hw/alpha/pci.c | 91 | ||||
-rw-r--r-- | hw/alpha/trace-events | 4 | ||||
-rw-r--r-- | hw/alpha/trace.h | 1 | ||||
-rw-r--r-- | hw/alpha/typhoon.c | 952 |
8 files changed, 1310 insertions, 0 deletions
diff --git a/hw/alpha/Kconfig b/hw/alpha/Kconfig new file mode 100644 index 000000000..9af650c94 --- /dev/null +++ b/hw/alpha/Kconfig @@ -0,0 +1,11 @@ +config DP264 + bool + imply PCI_DEVICES + imply TEST_DEVICES + imply E1000_PCI + select I82378 + select IDE_CMD646 + select MC146818RTC + select PCI + select PCKBD + select SMC37C669 diff --git a/hw/alpha/alpha_sys.h b/hw/alpha/alpha_sys.h new file mode 100644 index 000000000..2263e821d --- /dev/null +++ b/hw/alpha/alpha_sys.h @@ -0,0 +1,21 @@ +/* Alpha cores and system support chips. */ + +#ifndef HW_ALPHA_SYS_H +#define HW_ALPHA_SYS_H + +#include "target/alpha/cpu-qom.h" +#include "hw/pci/pci.h" +#include "hw/pci/pci_host.h" +#include "hw/boards.h" +#include "hw/intc/i8259.h" + + +PCIBus *typhoon_init(MemoryRegion *, qemu_irq *, qemu_irq *, AlphaCPU *[4], + pci_map_irq_fn, uint8_t devfn_min); + +/* alpha_pci.c. */ +extern const MemoryRegionOps alpha_pci_ignore_ops; +extern const MemoryRegionOps alpha_pci_conf1_ops; +extern const MemoryRegionOps alpha_pci_iack_ops; + +#endif diff --git a/hw/alpha/dp264.c b/hw/alpha/dp264.c new file mode 100644 index 000000000..c78ed96d0 --- /dev/null +++ b/hw/alpha/dp264.c @@ -0,0 +1,222 @@ +/* + * QEMU Alpha DP264/CLIPPER hardware system emulator. + * + * Choose CLIPPER IRQ mappings over, say, DP264, MONET, or WEBBRICK + * variants because CLIPPER doesn't have an SMC669 SuperIO controller + * that we need to emulate as well. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "cpu.h" +#include "elf.h" +#include "hw/loader.h" +#include "alpha_sys.h" +#include "qemu/error-report.h" +#include "hw/rtc/mc146818rtc.h" +#include "hw/ide/pci.h" +#include "hw/isa/superio.h" +#include "net/net.h" +#include "qemu/cutils.h" +#include "qemu/datadir.h" +#include "net/net.h" + +#define MAX_IDE_BUS 2 + +static uint64_t cpu_alpha_superpage_to_phys(void *opaque, uint64_t addr) +{ + if (((addr >> 41) & 3) == 2) { + addr &= 0xffffffffffull; + } + return addr; +} + +/* Note that there are at least 3 viewpoints of IRQ numbers on Alpha systems. + (0) The dev_irq_n lines into the cpu, which we totally ignore, + (1) The DRIR lines in the typhoon chipset, + (2) The "vector" aka mangled interrupt number reported by SRM PALcode, + (3) The interrupt number assigned by the kernel. + The following function is concerned with (1) only. */ + +static int clipper_pci_map_irq(PCIDevice *d, int irq_num) +{ + int slot = d->devfn >> 3; + + assert(irq_num >= 0 && irq_num <= 3); + + return (slot + 1) * 4 + irq_num; +} + +static void clipper_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + AlphaCPU *cpus[4]; + PCIBus *pci_bus; + PCIDevice *pci_dev; + DeviceState *i82378_dev; + ISABus *isa_bus; + qemu_irq rtc_irq; + qemu_irq isa_irq; + long size, i; + char *palcode_filename; + uint64_t palcode_entry; + uint64_t kernel_entry, kernel_low; + unsigned int smp_cpus = machine->smp.cpus; + + /* Create up to 4 cpus. */ + memset(cpus, 0, sizeof(cpus)); + for (i = 0; i < smp_cpus; ++i) { + cpus[i] = ALPHA_CPU(cpu_create(machine->cpu_type)); + } + + /* + * arg0 -> memory size + * arg1 -> kernel entry point + * arg2 -> config word + * + * Config word: bits 0-5 -> ncpus + * bit 6 -> nographics option (for HWRPB CTB) + * + * See init_hwrpb() in the PALcode. + */ + cpus[0]->env.trap_arg0 = ram_size; + cpus[0]->env.trap_arg1 = 0; + cpus[0]->env.trap_arg2 = smp_cpus | (!machine->enable_graphics << 6); + + /* + * Init the chipset. Because we're using CLIPPER IRQ mappings, + * the minimum PCI device IdSel is 1. + */ + pci_bus = typhoon_init(machine->ram, &isa_irq, &rtc_irq, cpus, + clipper_pci_map_irq, PCI_DEVFN(1, 0)); + + /* + * Init the PCI -> ISA bridge. + * + * Technically, PCI-based Alphas shipped with one of three different + * PCI-ISA bridges: + * + * - Intel i82378 SIO + * - Cypress CY82c693UB + * - ALI M1533 + * + * (An Intel i82375 PCI-EISA bridge was also used on some models.) + * + * For simplicity, we model an i82378 here, even though it wouldn't + * have been on any Tsunami/Typhoon systems; it's close enough, and + * we don't want to deal with modelling the CY82c693UB (which has + * incompatible edge/level control registers, plus other peripherals + * like IDE and USB) or the M1533 (which also has IDE and USB). + * + * Importantly, we need to provide a PCI device node for it, otherwise + * some operating systems won't notice there's an ISA bus to configure. + */ + i82378_dev = DEVICE(pci_create_simple(pci_bus, PCI_DEVFN(7, 0), "i82378")); + isa_bus = ISA_BUS(qdev_get_child_bus(i82378_dev, "isa.0")); + + /* Connect the ISA PIC to the Typhoon IRQ used for ISA interrupts. */ + qdev_connect_gpio_out(i82378_dev, 0, isa_irq); + + /* Since we have an SRM-compatible PALcode, use the SRM epoch. */ + mc146818_rtc_init(isa_bus, 1900, rtc_irq); + + /* VGA setup. Don't bother loading the bios. */ + pci_vga_init(pci_bus); + + /* Network setup. e1000 is good enough, failing Tulip support. */ + for (i = 0; i < nb_nics; i++) { + pci_nic_init_nofail(&nd_table[i], pci_bus, "e1000", NULL); + } + + /* Super I/O */ + isa_create_simple(isa_bus, TYPE_SMC37C669_SUPERIO); + + /* IDE disk setup. */ + pci_dev = pci_create_simple(pci_bus, -1, "cmd646-ide"); + pci_ide_create_devs(pci_dev); + + /* Load PALcode. Given that this is not "real" cpu palcode, + but one explicitly written for the emulation, we might as + well load it directly from and ELF image. */ + palcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, + machine->firmware ?: "palcode-clipper"); + if (palcode_filename == NULL) { + error_report("no palcode provided"); + exit(1); + } + size = load_elf(palcode_filename, NULL, cpu_alpha_superpage_to_phys, + NULL, &palcode_entry, NULL, NULL, NULL, + 0, EM_ALPHA, 0, 0); + if (size < 0) { + error_report("could not load palcode '%s'", palcode_filename); + exit(1); + } + g_free(palcode_filename); + + /* Start all cpus at the PALcode RESET entry point. */ + for (i = 0; i < smp_cpus; ++i) { + cpus[i]->env.pc = palcode_entry; + cpus[i]->env.palbr = palcode_entry; + } + + /* Load a kernel. */ + if (kernel_filename) { + uint64_t param_offset; + + size = load_elf(kernel_filename, NULL, cpu_alpha_superpage_to_phys, + NULL, &kernel_entry, &kernel_low, NULL, NULL, + 0, EM_ALPHA, 0, 0); + if (size < 0) { + error_report("could not load kernel '%s'", kernel_filename); + exit(1); + } + + cpus[0]->env.trap_arg1 = kernel_entry; + + param_offset = kernel_low - 0x6000; + + if (kernel_cmdline) { + pstrcpy_targphys("cmdline", param_offset, 0x100, kernel_cmdline); + } + + if (initrd_filename) { + long initrd_base; + int64_t initrd_size; + + initrd_size = get_image_size(initrd_filename); + if (initrd_size < 0) { + error_report("could not load initial ram disk '%s'", + initrd_filename); + exit(1); + } + + /* Put the initrd image as high in memory as possible. */ + initrd_base = (ram_size - initrd_size) & TARGET_PAGE_MASK; + load_image_targphys(initrd_filename, initrd_base, + ram_size - initrd_base); + + address_space_stq(&address_space_memory, param_offset + 0x100, + initrd_base + 0xfffffc0000000000ULL, + MEMTXATTRS_UNSPECIFIED, + NULL); + address_space_stq(&address_space_memory, param_offset + 0x108, + initrd_size, MEMTXATTRS_UNSPECIFIED, NULL); + } + } +} + +static void clipper_machine_init(MachineClass *mc) +{ + mc->desc = "Alpha DP264/CLIPPER"; + mc->init = clipper_init; + mc->block_default_type = IF_IDE; + mc->max_cpus = 4; + mc->is_default = true; + mc->default_cpu_type = ALPHA_CPU_TYPE_NAME("ev67"); + mc->default_ram_id = "ram"; +} + +DEFINE_MACHINE("clipper", clipper_machine_init) diff --git a/hw/alpha/meson.build b/hw/alpha/meson.build new file mode 100644 index 000000000..81ca21577 --- /dev/null +++ b/hw/alpha/meson.build @@ -0,0 +1,8 @@ +alpha_ss = ss.source_set() +alpha_ss.add(when: 'CONFIG_DP264', if_true: files( + 'dp264.c', + 'pci.c', + 'typhoon.c', +)) + +hw_arch += {'alpha': alpha_ss} diff --git a/hw/alpha/pci.c b/hw/alpha/pci.c new file mode 100644 index 000000000..72251fcdf --- /dev/null +++ b/hw/alpha/pci.c @@ -0,0 +1,91 @@ +/* + * QEMU Alpha PCI support functions. + * + * Some of this isn't very Alpha specific at all. + * + * ??? Sparse memory access not implemented. + */ + +#include "qemu/osdep.h" +#include "alpha_sys.h" +#include "qemu/log.h" +#include "trace.h" + + +/* Fallback for unassigned PCI I/O operations. Avoids MCHK. */ + +static uint64_t ignore_read(void *opaque, hwaddr addr, unsigned size) +{ + return 0; +} + +static void ignore_write(void *opaque, hwaddr addr, uint64_t v, unsigned size) +{ +} + +const MemoryRegionOps alpha_pci_ignore_ops = { + .read = ignore_read, + .write = ignore_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + + +/* PCI config space reads/writes, to byte-word addressable memory. */ +static uint64_t bw_conf1_read(void *opaque, hwaddr addr, + unsigned size) +{ + PCIBus *b = opaque; + return pci_data_read(b, addr, size); +} + +static void bw_conf1_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + PCIBus *b = opaque; + pci_data_write(b, addr, val, size); +} + +const MemoryRegionOps alpha_pci_conf1_ops = { + .read = bw_conf1_read, + .write = bw_conf1_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +/* PCI/EISA Interrupt Acknowledge Cycle. */ + +static uint64_t iack_read(void *opaque, hwaddr addr, unsigned size) +{ + return pic_read_irq(isa_pic); +} + +static void special_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + trace_alpha_pci_iack_write(); +} + +const MemoryRegionOps alpha_pci_iack_ops = { + .read = iack_read, + .write = special_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; diff --git a/hw/alpha/trace-events b/hw/alpha/trace-events new file mode 100644 index 000000000..952a81640 --- /dev/null +++ b/hw/alpha/trace-events @@ -0,0 +1,4 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# pci.c +alpha_pci_iack_write(void) "" diff --git a/hw/alpha/trace.h b/hw/alpha/trace.h new file mode 100644 index 000000000..20fe69819 --- /dev/null +++ b/hw/alpha/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_alpha.h" diff --git a/hw/alpha/typhoon.c b/hw/alpha/typhoon.c new file mode 100644 index 000000000..bd39c8ca8 --- /dev/null +++ b/hw/alpha/typhoon.c @@ -0,0 +1,952 @@ +/* + * DEC 21272 (TSUNAMI/TYPHOON) chipset emulation. + * + * Written by Richard Henderson. + * + * This work is licensed under the GNU GPL license version 2 or later. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "cpu.h" +#include "hw/irq.h" +#include "alpha_sys.h" +#include "qom/object.h" + + +#define TYPE_TYPHOON_PCI_HOST_BRIDGE "typhoon-pcihost" +#define TYPE_TYPHOON_IOMMU_MEMORY_REGION "typhoon-iommu-memory-region" + +typedef struct TyphoonCchip { + MemoryRegion region; + uint64_t misc; + uint64_t drir; + uint64_t dim[4]; + uint32_t iic[4]; + AlphaCPU *cpu[4]; +} TyphoonCchip; + +typedef struct TyphoonWindow { + uint64_t wba; + uint64_t wsm; + uint64_t tba; +} TyphoonWindow; + +typedef struct TyphoonPchip { + MemoryRegion region; + MemoryRegion reg_iack; + MemoryRegion reg_mem; + MemoryRegion reg_io; + MemoryRegion reg_conf; + + AddressSpace iommu_as; + IOMMUMemoryRegion iommu; + + uint64_t ctl; + TyphoonWindow win[4]; +} TyphoonPchip; + +OBJECT_DECLARE_SIMPLE_TYPE(TyphoonState, TYPHOON_PCI_HOST_BRIDGE) + +struct TyphoonState { + PCIHostState parent_obj; + + TyphoonCchip cchip; + TyphoonPchip pchip; + MemoryRegion dchip_region; +}; + +/* Called when one of DRIR or DIM changes. */ +static void cpu_irq_change(AlphaCPU *cpu, uint64_t req) +{ + /* If there are any non-masked interrupts, tell the cpu. */ + if (cpu != NULL) { + CPUState *cs = CPU(cpu); + if (req) { + cpu_interrupt(cs, CPU_INTERRUPT_HARD); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD); + } + } +} + +static MemTxResult cchip_read(void *opaque, hwaddr addr, + uint64_t *data, unsigned size, + MemTxAttrs attrs) +{ + CPUState *cpu = current_cpu; + TyphoonState *s = opaque; + uint64_t ret = 0; + + switch (addr) { + case 0x0000: + /* CSC: Cchip System Configuration Register. */ + /* All sorts of data here; probably the only thing relevant is + PIP<14> Pchip 1 Present = 0. */ + break; + + case 0x0040: + /* MTR: Memory Timing Register. */ + /* All sorts of stuff related to real DRAM. */ + break; + + case 0x0080: + /* MISC: Miscellaneous Register. */ + ret = s->cchip.misc | (cpu->cpu_index & 3); + break; + + case 0x00c0: + /* MPD: Memory Presence Detect Register. */ + break; + + case 0x0100: /* AAR0 */ + case 0x0140: /* AAR1 */ + case 0x0180: /* AAR2 */ + case 0x01c0: /* AAR3 */ + /* AAR: Array Address Register. */ + /* All sorts of information about DRAM. */ + break; + + case 0x0200: + /* DIM0: Device Interrupt Mask Register, CPU0. */ + ret = s->cchip.dim[0]; + break; + case 0x0240: + /* DIM1: Device Interrupt Mask Register, CPU1. */ + ret = s->cchip.dim[1]; + break; + case 0x0280: + /* DIR0: Device Interrupt Request Register, CPU0. */ + ret = s->cchip.dim[0] & s->cchip.drir; + break; + case 0x02c0: + /* DIR1: Device Interrupt Request Register, CPU1. */ + ret = s->cchip.dim[1] & s->cchip.drir; + break; + case 0x0300: + /* DRIR: Device Raw Interrupt Request Register. */ + ret = s->cchip.drir; + break; + + case 0x0340: + /* PRBEN: Probe Enable Register. */ + break; + + case 0x0380: + /* IIC0: Interval Ignore Count Register, CPU0. */ + ret = s->cchip.iic[0]; + break; + case 0x03c0: + /* IIC1: Interval Ignore Count Register, CPU1. */ + ret = s->cchip.iic[1]; + break; + + case 0x0400: /* MPR0 */ + case 0x0440: /* MPR1 */ + case 0x0480: /* MPR2 */ + case 0x04c0: /* MPR3 */ + /* MPR: Memory Programming Register. */ + break; + + case 0x0580: + /* TTR: TIGbus Timing Register. */ + /* All sorts of stuff related to interrupt delivery timings. */ + break; + case 0x05c0: + /* TDR: TIGbug Device Timing Register. */ + break; + + case 0x0600: + /* DIM2: Device Interrupt Mask Register, CPU2. */ + ret = s->cchip.dim[2]; + break; + case 0x0640: + /* DIM3: Device Interrupt Mask Register, CPU3. */ + ret = s->cchip.dim[3]; + break; + case 0x0680: + /* DIR2: Device Interrupt Request Register, CPU2. */ + ret = s->cchip.dim[2] & s->cchip.drir; + break; + case 0x06c0: + /* DIR3: Device Interrupt Request Register, CPU3. */ + ret = s->cchip.dim[3] & s->cchip.drir; + break; + + case 0x0700: + /* IIC2: Interval Ignore Count Register, CPU2. */ + ret = s->cchip.iic[2]; + break; + case 0x0740: + /* IIC3: Interval Ignore Count Register, CPU3. */ + ret = s->cchip.iic[3]; + break; + + case 0x0780: + /* PWR: Power Management Control. */ + break; + + case 0x0c00: /* CMONCTLA */ + case 0x0c40: /* CMONCTLB */ + case 0x0c80: /* CMONCNT01 */ + case 0x0cc0: /* CMONCNT23 */ + break; + + default: + return MEMTX_ERROR; + } + + *data = ret; + return MEMTX_OK; +} + +static uint64_t dchip_read(void *opaque, hwaddr addr, unsigned size) +{ + /* Skip this. It's all related to DRAM timing and setup. */ + return 0; +} + +static MemTxResult pchip_read(void *opaque, hwaddr addr, uint64_t *data, + unsigned size, MemTxAttrs attrs) +{ + TyphoonState *s = opaque; + uint64_t ret = 0; + + switch (addr) { + case 0x0000: + /* WSBA0: Window Space Base Address Register. */ + ret = s->pchip.win[0].wba; + break; + case 0x0040: + /* WSBA1 */ + ret = s->pchip.win[1].wba; + break; + case 0x0080: + /* WSBA2 */ + ret = s->pchip.win[2].wba; + break; + case 0x00c0: + /* WSBA3 */ + ret = s->pchip.win[3].wba; + break; + + case 0x0100: + /* WSM0: Window Space Mask Register. */ + ret = s->pchip.win[0].wsm; + break; + case 0x0140: + /* WSM1 */ + ret = s->pchip.win[1].wsm; + break; + case 0x0180: + /* WSM2 */ + ret = s->pchip.win[2].wsm; + break; + case 0x01c0: + /* WSM3 */ + ret = s->pchip.win[3].wsm; + break; + + case 0x0200: + /* TBA0: Translated Base Address Register. */ + ret = s->pchip.win[0].tba; + break; + case 0x0240: + /* TBA1 */ + ret = s->pchip.win[1].tba; + break; + case 0x0280: + /* TBA2 */ + ret = s->pchip.win[2].tba; + break; + case 0x02c0: + /* TBA3 */ + ret = s->pchip.win[3].tba; + break; + + case 0x0300: + /* PCTL: Pchip Control Register. */ + ret = s->pchip.ctl; + break; + case 0x0340: + /* PLAT: Pchip Master Latency Register. */ + break; + case 0x03c0: + /* PERROR: Pchip Error Register. */ + break; + case 0x0400: + /* PERRMASK: Pchip Error Mask Register. */ + break; + case 0x0440: + /* PERRSET: Pchip Error Set Register. */ + break; + case 0x0480: + /* TLBIV: Translation Buffer Invalidate Virtual Register (WO). */ + break; + case 0x04c0: + /* TLBIA: Translation Buffer Invalidate All Register (WO). */ + break; + case 0x0500: /* PMONCTL */ + case 0x0540: /* PMONCNT */ + case 0x0800: /* SPRST */ + break; + + default: + return MEMTX_ERROR; + } + + *data = ret; + return MEMTX_OK; +} + +static MemTxResult cchip_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size, + MemTxAttrs attrs) +{ + TyphoonState *s = opaque; + uint64_t oldval, newval; + + switch (addr) { + case 0x0000: + /* CSC: Cchip System Configuration Register. */ + /* All sorts of data here; nothing relevant RW. */ + break; + + case 0x0040: + /* MTR: Memory Timing Register. */ + /* All sorts of stuff related to real DRAM. */ + break; + + case 0x0080: + /* MISC: Miscellaneous Register. */ + newval = oldval = s->cchip.misc; + newval &= ~(val & 0x10000ff0); /* W1C fields */ + if (val & 0x100000) { + newval &= ~0xff0000ull; /* ACL clears ABT and ABW */ + } else { + newval |= val & 0x00f00000; /* ABT field is W1S */ + if ((newval & 0xf0000) == 0) { + newval |= val & 0xf0000; /* ABW field is W1S iff zero */ + } + } + newval |= (val & 0xf000) >> 4; /* IPREQ field sets IPINTR. */ + + newval &= ~0xf0000000000ull; /* WO and RW fields */ + newval |= val & 0xf0000000000ull; + s->cchip.misc = newval; + + /* Pass on changes to IPI and ITI state. */ + if ((newval ^ oldval) & 0xff0) { + int i; + for (i = 0; i < 4; ++i) { + AlphaCPU *cpu = s->cchip.cpu[i]; + if (cpu != NULL) { + CPUState *cs = CPU(cpu); + /* IPI can be either cleared or set by the write. */ + if (newval & (1 << (i + 8))) { + cpu_interrupt(cs, CPU_INTERRUPT_SMP); + } else { + cpu_reset_interrupt(cs, CPU_INTERRUPT_SMP); + } + + /* ITI can only be cleared by the write. */ + if ((newval & (1 << (i + 4))) == 0) { + cpu_reset_interrupt(cs, CPU_INTERRUPT_TIMER); + } + } + } + } + break; + + case 0x00c0: + /* MPD: Memory Presence Detect Register. */ + break; + + case 0x0100: /* AAR0 */ + case 0x0140: /* AAR1 */ + case 0x0180: /* AAR2 */ + case 0x01c0: /* AAR3 */ + /* AAR: Array Address Register. */ + /* All sorts of information about DRAM. */ + break; + + case 0x0200: /* DIM0 */ + /* DIM: Device Interrupt Mask Register, CPU0. */ + s->cchip.dim[0] = val; + cpu_irq_change(s->cchip.cpu[0], val & s->cchip.drir); + break; + case 0x0240: /* DIM1 */ + /* DIM: Device Interrupt Mask Register, CPU1. */ + s->cchip.dim[1] = val; + cpu_irq_change(s->cchip.cpu[1], val & s->cchip.drir); + break; + + case 0x0280: /* DIR0 (RO) */ + case 0x02c0: /* DIR1 (RO) */ + case 0x0300: /* DRIR (RO) */ + break; + + case 0x0340: + /* PRBEN: Probe Enable Register. */ + break; + + case 0x0380: /* IIC0 */ + s->cchip.iic[0] = val & 0xffffff; + break; + case 0x03c0: /* IIC1 */ + s->cchip.iic[1] = val & 0xffffff; + break; + + case 0x0400: /* MPR0 */ + case 0x0440: /* MPR1 */ + case 0x0480: /* MPR2 */ + case 0x04c0: /* MPR3 */ + /* MPR: Memory Programming Register. */ + break; + + case 0x0580: + /* TTR: TIGbus Timing Register. */ + /* All sorts of stuff related to interrupt delivery timings. */ + break; + case 0x05c0: + /* TDR: TIGbug Device Timing Register. */ + break; + + case 0x0600: + /* DIM2: Device Interrupt Mask Register, CPU2. */ + s->cchip.dim[2] = val; + cpu_irq_change(s->cchip.cpu[2], val & s->cchip.drir); + break; + case 0x0640: + /* DIM3: Device Interrupt Mask Register, CPU3. */ + s->cchip.dim[3] = val; + cpu_irq_change(s->cchip.cpu[3], val & s->cchip.drir); + break; + + case 0x0680: /* DIR2 (RO) */ + case 0x06c0: /* DIR3 (RO) */ + break; + + case 0x0700: /* IIC2 */ + s->cchip.iic[2] = val & 0xffffff; + break; + case 0x0740: /* IIC3 */ + s->cchip.iic[3] = val & 0xffffff; + break; + + case 0x0780: + /* PWR: Power Management Control. */ + break; + + case 0x0c00: /* CMONCTLA */ + case 0x0c40: /* CMONCTLB */ + case 0x0c80: /* CMONCNT01 */ + case 0x0cc0: /* CMONCNT23 */ + break; + + default: + return MEMTX_ERROR; + } + + return MEMTX_OK; +} + +static void dchip_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + /* Skip this. It's all related to DRAM timing and setup. */ +} + +static MemTxResult pchip_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size, + MemTxAttrs attrs) +{ + TyphoonState *s = opaque; + uint64_t oldval; + + switch (addr) { + case 0x0000: + /* WSBA0: Window Space Base Address Register. */ + s->pchip.win[0].wba = val & 0xfff00003u; + break; + case 0x0040: + /* WSBA1 */ + s->pchip.win[1].wba = val & 0xfff00003u; + break; + case 0x0080: + /* WSBA2 */ + s->pchip.win[2].wba = val & 0xfff00003u; + break; + case 0x00c0: + /* WSBA3 */ + s->pchip.win[3].wba = (val & 0x80fff00001ull) | 2; + break; + + case 0x0100: + /* WSM0: Window Space Mask Register. */ + s->pchip.win[0].wsm = val & 0xfff00000u; + break; + case 0x0140: + /* WSM1 */ + s->pchip.win[1].wsm = val & 0xfff00000u; + break; + case 0x0180: + /* WSM2 */ + s->pchip.win[2].wsm = val & 0xfff00000u; + break; + case 0x01c0: + /* WSM3 */ + s->pchip.win[3].wsm = val & 0xfff00000u; + break; + + case 0x0200: + /* TBA0: Translated Base Address Register. */ + s->pchip.win[0].tba = val & 0x7fffffc00ull; + break; + case 0x0240: + /* TBA1 */ + s->pchip.win[1].tba = val & 0x7fffffc00ull; + break; + case 0x0280: + /* TBA2 */ + s->pchip.win[2].tba = val & 0x7fffffc00ull; + break; + case 0x02c0: + /* TBA3 */ + s->pchip.win[3].tba = val & 0x7fffffc00ull; + break; + + case 0x0300: + /* PCTL: Pchip Control Register. */ + oldval = s->pchip.ctl; + oldval &= ~0x00001cff0fc7ffull; /* RW fields */ + oldval |= val & 0x00001cff0fc7ffull; + s->pchip.ctl = oldval; + break; + + case 0x0340: + /* PLAT: Pchip Master Latency Register. */ + break; + case 0x03c0: + /* PERROR: Pchip Error Register. */ + break; + case 0x0400: + /* PERRMASK: Pchip Error Mask Register. */ + break; + case 0x0440: + /* PERRSET: Pchip Error Set Register. */ + break; + + case 0x0480: + /* TLBIV: Translation Buffer Invalidate Virtual Register. */ + break; + + case 0x04c0: + /* TLBIA: Translation Buffer Invalidate All Register (WO). */ + break; + + case 0x0500: + /* PMONCTL */ + case 0x0540: + /* PMONCNT */ + case 0x0800: + /* SPRST */ + break; + + default: + return MEMTX_ERROR; + } + + return MEMTX_OK; +} + +static const MemoryRegionOps cchip_ops = { + .read_with_attrs = cchip_read, + .write_with_attrs = cchip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 8, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 8, + .max_access_size = 8, + }, +}; + +static const MemoryRegionOps dchip_ops = { + .read = dchip_read, + .write = dchip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 8, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 8, + .max_access_size = 8, + }, +}; + +static const MemoryRegionOps pchip_ops = { + .read_with_attrs = pchip_read, + .write_with_attrs = pchip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 8, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 8, + .max_access_size = 8, + }, +}; + +/* A subroutine of typhoon_translate_iommu that builds an IOMMUTLBEntry + using the given translated address and mask. */ +static bool make_iommu_tlbe(hwaddr taddr, hwaddr mask, IOMMUTLBEntry *ret) +{ + *ret = (IOMMUTLBEntry) { + .target_as = &address_space_memory, + .translated_addr = taddr, + .addr_mask = mask, + .perm = IOMMU_RW, + }; + return true; +} + +/* A subroutine of typhoon_translate_iommu that handles scatter-gather + translation, given the address of the PTE. */ +static bool pte_translate(hwaddr pte_addr, IOMMUTLBEntry *ret) +{ + uint64_t pte = address_space_ldq(&address_space_memory, pte_addr, + MEMTXATTRS_UNSPECIFIED, NULL); + + /* Check valid bit. */ + if ((pte & 1) == 0) { + return false; + } + + return make_iommu_tlbe((pte & 0x3ffffe) << 12, 0x1fff, ret); +} + +/* A subroutine of typhoon_translate_iommu that handles one of the + four single-address-cycle translation windows. */ +static bool window_translate(TyphoonWindow *win, hwaddr addr, + IOMMUTLBEntry *ret) +{ + uint32_t wba = win->wba; + uint64_t wsm = win->wsm; + uint64_t tba = win->tba; + uint64_t wsm_ext = wsm | 0xfffff; + + /* Check for window disabled. */ + if ((wba & 1) == 0) { + return false; + } + + /* Check for window hit. */ + if ((addr & ~wsm_ext) != (wba & 0xfff00000u)) { + return false; + } + + if (wba & 2) { + /* Scatter-gather translation. */ + hwaddr pte_addr; + + /* See table 10-6, Generating PTE address for PCI DMA Address. */ + pte_addr = tba & ~(wsm >> 10); + pte_addr |= (addr & (wsm | 0xfe000)) >> 10; + return pte_translate(pte_addr, ret); + } else { + /* Direct-mapped translation. */ + return make_iommu_tlbe(tba & ~wsm_ext, wsm_ext, ret); + } +} + +/* Handle PCI-to-system address translation. */ +/* TODO: A translation failure here ought to set PCI error codes on the + Pchip and generate a machine check interrupt. */ +static IOMMUTLBEntry typhoon_translate_iommu(IOMMUMemoryRegion *iommu, + hwaddr addr, + IOMMUAccessFlags flag, + int iommu_idx) +{ + TyphoonPchip *pchip = container_of(iommu, TyphoonPchip, iommu); + IOMMUTLBEntry ret; + int i; + + if (addr <= 0xffffffffu) { + /* Single-address cycle. */ + + /* Check for the Window Hole, inhibiting matching. */ + if ((pchip->ctl & 0x20) + && addr >= 0x80000 + && addr <= 0xfffff) { + goto failure; + } + + /* Check the first three windows. */ + for (i = 0; i < 3; ++i) { + if (window_translate(&pchip->win[i], addr, &ret)) { + goto success; + } + } + + /* Check the fourth window for DAC disable. */ + if ((pchip->win[3].wba & 0x80000000000ull) == 0 + && window_translate(&pchip->win[3], addr, &ret)) { + goto success; + } + } else { + /* Double-address cycle. */ + + if (addr >= 0x10000000000ull && addr < 0x20000000000ull) { + /* Check for the DMA monster window. */ + if (pchip->ctl & 0x40) { + /* See 10.1.4.4; in particular <39:35> is ignored. */ + make_iommu_tlbe(0, 0x007ffffffffull, &ret); + goto success; + } + } + + if (addr >= 0x80000000000ull && addr <= 0xfffffffffffull) { + /* Check the fourth window for DAC enable and window enable. */ + if ((pchip->win[3].wba & 0x80000000001ull) == 0x80000000001ull) { + uint64_t pte_addr; + + pte_addr = pchip->win[3].tba & 0x7ffc00000ull; + pte_addr |= (addr & 0xffffe000u) >> 10; + if (pte_translate(pte_addr, &ret)) { + goto success; + } + } + } + } + + failure: + ret = (IOMMUTLBEntry) { .perm = IOMMU_NONE }; + success: + return ret; +} + +static AddressSpace *typhoon_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn) +{ + TyphoonState *s = opaque; + return &s->pchip.iommu_as; +} + +static void typhoon_set_irq(void *opaque, int irq, int level) +{ + TyphoonState *s = opaque; + uint64_t drir; + int i; + + /* Set/Reset the bit in CCHIP.DRIR based on IRQ+LEVEL. */ + drir = s->cchip.drir; + if (level) { + drir |= 1ull << irq; + } else { + drir &= ~(1ull << irq); + } + s->cchip.drir = drir; + + for (i = 0; i < 4; ++i) { + cpu_irq_change(s->cchip.cpu[i], s->cchip.dim[i] & drir); + } +} + +static void typhoon_set_isa_irq(void *opaque, int irq, int level) +{ + typhoon_set_irq(opaque, 55, level); +} + +static void typhoon_set_timer_irq(void *opaque, int irq, int level) +{ + TyphoonState *s = opaque; + int i; + + /* Thankfully, the mc146818rtc code doesn't track the IRQ state, + and so we don't have to worry about missing interrupts just + because we never actually ACK the interrupt. Just ignore any + case of the interrupt level going low. */ + if (level == 0) { + return; + } + + /* Deliver the interrupt to each CPU, considering each CPU's IIC. */ + for (i = 0; i < 4; ++i) { + AlphaCPU *cpu = s->cchip.cpu[i]; + if (cpu != NULL) { + uint32_t iic = s->cchip.iic[i]; + + /* ??? The verbage in Section 10.2.2.10 isn't 100% clear. + Bit 24 is the OverFlow bit, RO, and set when the count + decrements past 0. When is OF cleared? My guess is that + OF is actually cleared when the IIC is written, and that + the ICNT field always decrements. At least, that's an + interpretation that makes sense, and "allows the CPU to + determine exactly how mant interval timer ticks were + skipped". At least within the next 4M ticks... */ + + iic = ((iic - 1) & 0x1ffffff) | (iic & 0x1000000); + s->cchip.iic[i] = iic; + + if (iic & 0x1000000) { + /* Set the ITI bit for this cpu. */ + s->cchip.misc |= 1 << (i + 4); + /* And signal the interrupt. */ + cpu_interrupt(CPU(cpu), CPU_INTERRUPT_TIMER); + } + } + } +} + +static void typhoon_alarm_timer(void *opaque) +{ + TyphoonState *s = (TyphoonState *)((uintptr_t)opaque & ~3); + int cpu = (uintptr_t)opaque & 3; + + /* Set the ITI bit for this cpu. */ + s->cchip.misc |= 1 << (cpu + 4); + cpu_interrupt(CPU(s->cchip.cpu[cpu]), CPU_INTERRUPT_TIMER); +} + +PCIBus *typhoon_init(MemoryRegion *ram, qemu_irq *p_isa_irq, + qemu_irq *p_rtc_irq, AlphaCPU *cpus[4], + pci_map_irq_fn sys_map_irq, uint8_t devfn_min) +{ + MemoryRegion *addr_space = get_system_memory(); + DeviceState *dev; + TyphoonState *s; + PCIHostState *phb; + PCIBus *b; + int i; + + dev = qdev_new(TYPE_TYPHOON_PCI_HOST_BRIDGE); + + s = TYPHOON_PCI_HOST_BRIDGE(dev); + phb = PCI_HOST_BRIDGE(dev); + + s->cchip.misc = 0x800000000ull; /* Revision: Typhoon. */ + s->pchip.win[3].wba = 2; /* Window 3 SG always enabled. */ + + /* Remember the CPUs so that we can deliver interrupts to them. */ + for (i = 0; i < 4; i++) { + AlphaCPU *cpu = cpus[i]; + s->cchip.cpu[i] = cpu; + if (cpu != NULL) { + cpu->alarm_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + typhoon_alarm_timer, + (void *)((uintptr_t)s + i)); + } + } + + *p_isa_irq = qemu_allocate_irq(typhoon_set_isa_irq, s, 0); + *p_rtc_irq = qemu_allocate_irq(typhoon_set_timer_irq, s, 0); + + /* Main memory region, 0x00.0000.0000. Real hardware supports 32GB, + but the address space hole reserved at this point is 8TB. */ + memory_region_add_subregion(addr_space, 0, ram); + + /* TIGbus, 0x801.0000.0000, 1GB. */ + /* ??? The TIGbus is used for delivering interrupts, and access to + the flash ROM. I'm not sure that we need to implement it at all. */ + + /* Pchip0 CSRs, 0x801.8000.0000, 256MB. */ + memory_region_init_io(&s->pchip.region, OBJECT(s), &pchip_ops, s, "pchip0", + 256 * MiB); + memory_region_add_subregion(addr_space, 0x80180000000ULL, + &s->pchip.region); + + /* Cchip CSRs, 0x801.A000.0000, 256MB. */ + memory_region_init_io(&s->cchip.region, OBJECT(s), &cchip_ops, s, "cchip0", + 256 * MiB); + memory_region_add_subregion(addr_space, 0x801a0000000ULL, + &s->cchip.region); + + /* Dchip CSRs, 0x801.B000.0000, 256MB. */ + memory_region_init_io(&s->dchip_region, OBJECT(s), &dchip_ops, s, "dchip0", + 256 * MiB); + memory_region_add_subregion(addr_space, 0x801b0000000ULL, + &s->dchip_region); + + /* Pchip0 PCI memory, 0x800.0000.0000, 4GB. */ + memory_region_init(&s->pchip.reg_mem, OBJECT(s), "pci0-mem", 4 * GiB); + memory_region_add_subregion(addr_space, 0x80000000000ULL, + &s->pchip.reg_mem); + + /* Pchip0 PCI I/O, 0x801.FC00.0000, 32MB. */ + memory_region_init_io(&s->pchip.reg_io, OBJECT(s), &alpha_pci_ignore_ops, + NULL, "pci0-io", 32 * MiB); + memory_region_add_subregion(addr_space, 0x801fc000000ULL, + &s->pchip.reg_io); + + b = pci_register_root_bus(dev, "pci", + typhoon_set_irq, sys_map_irq, s, + &s->pchip.reg_mem, &s->pchip.reg_io, + devfn_min, 64, TYPE_PCI_BUS); + phb->bus = b; + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + /* Host memory as seen from the PCI side, via the IOMMU. */ + memory_region_init_iommu(&s->pchip.iommu, sizeof(s->pchip.iommu), + TYPE_TYPHOON_IOMMU_MEMORY_REGION, OBJECT(s), + "iommu-typhoon", UINT64_MAX); + address_space_init(&s->pchip.iommu_as, MEMORY_REGION(&s->pchip.iommu), + "pchip0-pci"); + pci_setup_iommu(b, typhoon_pci_dma_iommu, s); + + /* Pchip0 PCI special/interrupt acknowledge, 0x801.F800.0000, 64MB. */ + memory_region_init_io(&s->pchip.reg_iack, OBJECT(s), &alpha_pci_iack_ops, + b, "pci0-iack", 64 * MiB); + memory_region_add_subregion(addr_space, 0x801f8000000ULL, + &s->pchip.reg_iack); + + /* Pchip0 PCI configuration, 0x801.FE00.0000, 16MB. */ + memory_region_init_io(&s->pchip.reg_conf, OBJECT(s), &alpha_pci_conf1_ops, + b, "pci0-conf", 16 * MiB); + memory_region_add_subregion(addr_space, 0x801fe000000ULL, + &s->pchip.reg_conf); + + /* For the record, these are the mappings for the second PCI bus. + We can get away with not implementing them because we indicate + via the Cchip.CSC<PIP> bit that Pchip1 is not present. */ + /* Pchip1 PCI memory, 0x802.0000.0000, 4GB. */ + /* Pchip1 CSRs, 0x802.8000.0000, 256MB. */ + /* Pchip1 PCI special/interrupt acknowledge, 0x802.F800.0000, 64MB. */ + /* Pchip1 PCI I/O, 0x802.FC00.0000, 32MB. */ + /* Pchip1 PCI configuration, 0x802.FE00.0000, 16MB. */ + + return b; +} + +static const TypeInfo typhoon_pcihost_info = { + .name = TYPE_TYPHOON_PCI_HOST_BRIDGE, + .parent = TYPE_PCI_HOST_BRIDGE, + .instance_size = sizeof(TyphoonState), +}; + +static void typhoon_iommu_memory_region_class_init(ObjectClass *klass, + void *data) +{ + IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass); + + imrc->translate = typhoon_translate_iommu; +} + +static const TypeInfo typhoon_iommu_memory_region_info = { + .parent = TYPE_IOMMU_MEMORY_REGION, + .name = TYPE_TYPHOON_IOMMU_MEMORY_REGION, + .class_init = typhoon_iommu_memory_region_class_init, +}; + +static void typhoon_register_types(void) +{ + type_register_static(&typhoon_pcihost_info); + type_register_static(&typhoon_iommu_memory_region_info); +} + +type_init(typhoon_register_types) |