ad70198043
NetBSD accesses some astro and elroy registers which aren't accessed
by Linux yet. Add emulation for those registers to allow NetBSD to
boot further.
Please note that this patch is not sufficient to completely boot up
NetBSD on the 64-bit C3700 machine yet.
Signed-off-by: Helge Deller <deller@gmx.de>
Tested-by: Bruno Haible <bruno@clisp.org>
(cherry picked from commit 3b57c15f02
)
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
904 lines
28 KiB
C
904 lines
28 KiB
C
/*
|
|
* HP-PARISC Astro/Pluto/Ike/REO system bus adapter (SBA)
|
|
* with Elroy PCI bus (LBA) adapter emulation
|
|
* Found in C3000 and similar machines
|
|
*
|
|
* (C) 2023 by Helge Deller <deller@gmx.de>
|
|
*
|
|
* This work is licensed under the GNU GPL license version 2 or later.
|
|
*
|
|
* Chip documentation is available at:
|
|
* https://parisc.wiki.kernel.org/index.php/Technical_Documentation
|
|
*
|
|
* TODO:
|
|
* - All user-added devices are currently attached to the first
|
|
* Elroy (PCI bus) only for now. To fix this additional work in
|
|
* SeaBIOS and this driver is needed. See "user_creatable" flag below.
|
|
* - GMMIO (Greater than 4 GB MMIO) register
|
|
*/
|
|
|
|
#define TYPE_ASTRO_IOMMU_MEMORY_REGION "astro-iommu-memory-region"
|
|
|
|
#define F_EXTEND(addr) ((addr) | MAKE_64BIT_MASK(32, 32))
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/units.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/pci/pci_device.h"
|
|
#include "hw/pci/pci_bus.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/pci-host/astro.h"
|
|
#include "hw/hppa/hppa_hardware.h"
|
|
#include "migration/vmstate.h"
|
|
#include "target/hppa/cpu.h"
|
|
#include "trace.h"
|
|
#include "qom/object.h"
|
|
|
|
/*
|
|
* Helper functions
|
|
*/
|
|
|
|
static uint64_t mask_32bit_val(hwaddr addr, unsigned size, uint64_t val)
|
|
{
|
|
if (size == 8) {
|
|
return val;
|
|
}
|
|
if (addr & 4) {
|
|
val >>= 32;
|
|
} else {
|
|
val = (uint32_t) val;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void put_val_in_int64(uint64_t *p, hwaddr addr, unsigned size,
|
|
uint64_t val)
|
|
{
|
|
if (size == 8) {
|
|
*p = val;
|
|
} else if (size == 4) {
|
|
if (addr & 4) {
|
|
*p = ((*p << 32) >> 32) | (val << 32);
|
|
} else {
|
|
*p = ((*p >> 32) << 32) | (uint32_t) val;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void put_val_in_arrary(uint64_t *array, hwaddr start_addr,
|
|
hwaddr addr, unsigned size, uint64_t val)
|
|
{
|
|
int index;
|
|
|
|
index = (addr - start_addr) / 8;
|
|
put_val_in_int64(&array[index], addr, size, val);
|
|
}
|
|
|
|
|
|
/*
|
|
* The Elroy PCI host bridge. We have at least 4 of those under Astro.
|
|
*/
|
|
|
|
static MemTxResult elroy_chip_read_with_attrs(void *opaque, hwaddr addr,
|
|
uint64_t *data, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
MemTxResult ret = MEMTX_OK;
|
|
ElroyState *s = opaque;
|
|
uint64_t val = -1;
|
|
int index;
|
|
|
|
switch ((addr >> 3) << 3) {
|
|
case 0x0008:
|
|
val = 0x6000005; /* func_class */
|
|
break;
|
|
case 0x0058:
|
|
/*
|
|
* Scratch register, but firmware initializes it with the
|
|
* PCI BUS number and Linux/HP-UX uses it then.
|
|
*/
|
|
val = s->pci_bus_num;
|
|
/* Upper byte holds the end of this bus number */
|
|
val |= s->pci_bus_num << 8;
|
|
break;
|
|
case 0x0080:
|
|
val = s->arb_mask; /* set ARB mask */
|
|
break;
|
|
case 0x0108:
|
|
val = s->status_control;
|
|
break;
|
|
case 0x200 ... 0x250 - 1: /* LMMIO, GMMIO, WLMMIO, WGMMIO, ... */
|
|
index = (addr - 0x200) / 8;
|
|
val = s->mmio_base[index];
|
|
break;
|
|
case 0x0680:
|
|
val = s->error_config;
|
|
break;
|
|
case 0x0688:
|
|
val = 0; /* ERROR_STATUS */
|
|
break;
|
|
case 0x0800: /* IOSAPIC_REG_SELECT */
|
|
val = s->iosapic_reg_select;
|
|
break;
|
|
case 0x0808:
|
|
val = UINT64_MAX; /* XXX: tbc. */
|
|
g_assert_not_reached();
|
|
break;
|
|
case 0x0810: /* IOSAPIC_REG_WINDOW */
|
|
switch (s->iosapic_reg_select) {
|
|
case 0x01: /* IOSAPIC_REG_VERSION */
|
|
val = (32 << 16) | 1; /* upper 16bit holds max entries */
|
|
break;
|
|
default:
|
|
if (s->iosapic_reg_select < ARRAY_SIZE(s->iosapic_reg)) {
|
|
val = s->iosapic_reg[s->iosapic_reg_select];
|
|
} else {
|
|
trace_iosapic_reg_read(s->iosapic_reg_select, size, val);
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
trace_iosapic_reg_read(s->iosapic_reg_select, size, val);
|
|
break;
|
|
default:
|
|
trace_elroy_read(addr, size, val);
|
|
g_assert_not_reached();
|
|
}
|
|
trace_elroy_read(addr, size, val);
|
|
|
|
/* for 32-bit accesses mask return value */
|
|
val = mask_32bit_val(addr, size, val);
|
|
|
|
trace_astro_chip_read(addr, size, val);
|
|
*data = val;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static MemTxResult elroy_chip_write_with_attrs(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
ElroyState *s = opaque;
|
|
int i;
|
|
|
|
trace_elroy_write(addr, size, val);
|
|
|
|
switch ((addr >> 3) << 3) {
|
|
case 0x000: /* PCI_ID & PCI_COMMAND_STATUS_REG */
|
|
break;
|
|
case 0x080:
|
|
put_val_in_int64(&s->arb_mask, addr, size, val);
|
|
break;
|
|
case 0x0108:
|
|
put_val_in_int64(&s->status_control, addr, size, val);
|
|
break;
|
|
case 0x200 ... 0x250 - 1: /* LMMIO, GMMIO, WLMMIO, WGMMIO, ... */
|
|
put_val_in_arrary(s->mmio_base, 0x200, addr, size, val);
|
|
break;
|
|
case 0x300: /* ibase */
|
|
case 0x308: /* imask */
|
|
break;
|
|
case 0x0680:
|
|
put_val_in_int64(&s->error_config, addr, size, val);
|
|
break;
|
|
case 0x0800: /* IOSAPIC_REG_SELECT */
|
|
s->iosapic_reg_select = val;
|
|
break;
|
|
case 0x0810: /* IOSAPIC_REG_WINDOW */
|
|
trace_iosapic_reg_write(s->iosapic_reg_select, size, val);
|
|
if (s->iosapic_reg_select < ARRAY_SIZE(s->iosapic_reg)) {
|
|
s->iosapic_reg[s->iosapic_reg_select] = val;
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
break;
|
|
case 0x0840: /* IOSAPIC_REG_EOI */
|
|
val = le64_to_cpu(val);
|
|
val &= 63;
|
|
for (i = 0; i < ELROY_IRQS; i++) {
|
|
if ((s->iosapic_reg[0x10 + 2 * i] & 63) == val) {
|
|
s->ilr &= ~(1ull << i);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static const MemoryRegionOps elroy_chip_ops = {
|
|
.read_with_attrs = elroy_chip_read_with_attrs,
|
|
.write_with_attrs = elroy_chip_write_with_attrs,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
};
|
|
|
|
|
|
/* Unlike pci_config_data_le_ops, no check of high bit set in config_reg. */
|
|
|
|
static uint64_t elroy_config_data_read(void *opaque, hwaddr addr, unsigned len)
|
|
{
|
|
uint64_t val;
|
|
|
|
PCIHostState *s = opaque;
|
|
val = pci_data_read(s->bus, s->config_reg | (addr & 3), len);
|
|
trace_elroy_pci_config_data_read(s->config_reg | (addr & 3), len, val);
|
|
return val;
|
|
}
|
|
|
|
static void elroy_config_data_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned len)
|
|
{
|
|
PCIHostState *s = opaque;
|
|
pci_data_write(s->bus, s->config_reg | (addr & 3), val, len);
|
|
trace_elroy_pci_config_data_write(s->config_reg | (addr & 3), len, val);
|
|
}
|
|
|
|
static const MemoryRegionOps elroy_config_data_ops = {
|
|
.read = elroy_config_data_read,
|
|
.write = elroy_config_data_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static uint64_t elroy_config_addr_read(void *opaque, hwaddr addr, unsigned len)
|
|
{
|
|
ElroyState *s = opaque;
|
|
return s->config_reg_elroy;
|
|
}
|
|
|
|
static void elroy_config_addr_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned len)
|
|
{
|
|
PCIHostState *s = opaque;
|
|
ElroyState *es = opaque;
|
|
es->config_reg_elroy = val; /* keep a copy of original value */
|
|
s->config_reg = val;
|
|
}
|
|
|
|
static const MemoryRegionOps elroy_config_addr_ops = {
|
|
.read = elroy_config_addr_read,
|
|
.write = elroy_config_addr_write,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 8,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
|
|
/* Handle PCI-to-system address translation. */
|
|
static IOMMUTLBEntry astro_translate_iommu(IOMMUMemoryRegion *iommu,
|
|
hwaddr addr,
|
|
IOMMUAccessFlags flag,
|
|
int iommu_idx)
|
|
{
|
|
AstroState *s = container_of(iommu, AstroState, iommu);
|
|
hwaddr pdir_ptr, index, ibase;
|
|
hwaddr addr_mask = 0xfff; /* 4k translation */
|
|
uint64_t entry;
|
|
|
|
#define IOVP_SHIFT 12 /* equals PAGE_SHIFT */
|
|
#define PDIR_INDEX(iovp) ((iovp) >> IOVP_SHIFT)
|
|
#define SBA_PDIR_VALID_BIT 0x8000000000000000ULL
|
|
|
|
addr &= ~addr_mask;
|
|
|
|
/*
|
|
* Default translation: "32-bit PCI Addressing on 40-bit Runway".
|
|
* For addresses in the 32-bit memory address range ... and then
|
|
* language which not-coincidentally matches the PSW.W=0 mapping.
|
|
*/
|
|
if (addr <= UINT32_MAX) {
|
|
entry = hppa_abs_to_phys_pa2_w0(addr);
|
|
} else {
|
|
entry = addr;
|
|
}
|
|
|
|
/* "range enable" flag cleared? */
|
|
if ((s->tlb_ibase & 1) == 0) {
|
|
goto skip;
|
|
}
|
|
|
|
ibase = s->tlb_ibase & ~1ULL;
|
|
if ((addr & s->tlb_imask) != ibase) {
|
|
/* do not translate this one! */
|
|
goto skip;
|
|
}
|
|
|
|
index = PDIR_INDEX(addr);
|
|
pdir_ptr = s->tlb_pdir_base + index * sizeof(entry);
|
|
entry = ldq_le_phys(&address_space_memory, pdir_ptr);
|
|
|
|
if (!(entry & SBA_PDIR_VALID_BIT)) { /* I/O PDIR entry valid ? */
|
|
/* failure */
|
|
return (IOMMUTLBEntry) { .perm = IOMMU_NONE };
|
|
}
|
|
|
|
entry &= ~SBA_PDIR_VALID_BIT;
|
|
entry >>= IOVP_SHIFT;
|
|
entry <<= 12;
|
|
|
|
skip:
|
|
return (IOMMUTLBEntry) {
|
|
.target_as = &address_space_memory,
|
|
.iova = addr,
|
|
.translated_addr = entry,
|
|
.addr_mask = addr_mask,
|
|
.perm = IOMMU_RW,
|
|
};
|
|
}
|
|
|
|
static AddressSpace *elroy_pcihost_set_iommu(PCIBus *bus, void *opaque,
|
|
int devfn)
|
|
{
|
|
ElroyState *s = opaque;
|
|
return &s->astro->iommu_as;
|
|
}
|
|
|
|
static const PCIIOMMUOps elroy_pcihost_iommu_ops = {
|
|
.get_address_space = elroy_pcihost_set_iommu,
|
|
};
|
|
|
|
/*
|
|
* Encoding in IOSAPIC:
|
|
* base_addr == 0xfffa0000, we want to get 0xa0ff0000.
|
|
* eid 0x0ff00000 -> 0x00ff0000
|
|
* id 0x000ff000 -> 0xff000000
|
|
*/
|
|
#define SWIZZLE_HPA(a) \
|
|
((((a) & 0x0ff00000) >> 4) | (((a) & 0x000ff000) << 12))
|
|
#define UNSWIZZLE_HPA(a) \
|
|
(((((a) << 4) & 0x0ff00000) | (((a) >> 12) & 0x000ff000) | 0xf0000000))
|
|
|
|
/* bits in the "low" I/O Sapic IRdT entry */
|
|
#define IOSAPIC_IRDT_DISABLE 0x10000 /* if bit is set, mask this irq */
|
|
#define IOSAPIC_IRDT_PO_LOW 0x02000
|
|
#define IOSAPIC_IRDT_LEVEL_TRIG 0x08000
|
|
#define IOSAPIC_IRDT_MODE_LPRI 0x00100
|
|
|
|
#define CPU_IRQ_OFFSET 2
|
|
|
|
static void elroy_set_irq(void *opaque, int irq, int level)
|
|
{
|
|
ElroyState *s = opaque;
|
|
uint32_t bit;
|
|
uint32_t old_ilr = s->ilr;
|
|
hwaddr cpu_hpa;
|
|
uint32_t val;
|
|
|
|
val = s->iosapic_reg[0x10 + 2 * irq];
|
|
cpu_hpa = s->iosapic_reg[0x11 + 2 * irq];
|
|
/* low nibble of val has value to write into CPU irq reg */
|
|
bit = 1u << (val & (ELROY_IRQS - 1));
|
|
cpu_hpa = UNSWIZZLE_HPA(cpu_hpa);
|
|
|
|
if (level && (!(val & IOSAPIC_IRDT_DISABLE)) && cpu_hpa) {
|
|
uint32_t ena = bit & ~old_ilr;
|
|
s->ilr = old_ilr | bit;
|
|
if (ena != 0) {
|
|
stl_be_phys(&address_space_memory, F_EXTEND(cpu_hpa), val & 63);
|
|
}
|
|
} else {
|
|
s->ilr = old_ilr & ~bit;
|
|
}
|
|
}
|
|
|
|
static int elroy_pci_map_irq(PCIDevice *d, int irq_num)
|
|
{
|
|
int slot = PCI_SLOT(d->devfn);
|
|
|
|
assert(irq_num >= 0 && irq_num < ELROY_IRQS);
|
|
return slot & (ELROY_IRQS - 1);
|
|
}
|
|
|
|
static void elroy_reset(DeviceState *dev)
|
|
{
|
|
ElroyState *s = ELROY_PCI_HOST_BRIDGE(dev);
|
|
int irq;
|
|
|
|
/*
|
|
* Make sure to disable interrupts at reboot, otherwise the Linux kernel
|
|
* serial8250_config_port() in drivers/tty/serial/8250/8250_port.c
|
|
* will hang during autoconfig().
|
|
*/
|
|
s->ilr = 0;
|
|
for (irq = 0; irq < ELROY_IRQS; irq++) {
|
|
s->iosapic_reg[0x10 + 2 * irq] = IOSAPIC_IRDT_PO_LOW |
|
|
IOSAPIC_IRDT_LEVEL_TRIG | (irq + CPU_IRQ_OFFSET) |
|
|
IOSAPIC_IRDT_DISABLE;
|
|
s->iosapic_reg[0x11 + 2 * irq] = SWIZZLE_HPA(CPU_HPA);
|
|
}
|
|
}
|
|
|
|
static void elroy_pcihost_init(Object *obj)
|
|
{
|
|
ElroyState *s = ELROY_PCI_HOST_BRIDGE(obj);
|
|
PCIHostState *phb = PCI_HOST_BRIDGE(obj);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
|
|
/* Elroy config access from CPU. */
|
|
memory_region_init_io(&s->this_mem, OBJECT(s), &elroy_chip_ops,
|
|
s, "elroy", 0x2000);
|
|
|
|
/* Elroy PCI config. */
|
|
memory_region_init_io(&phb->conf_mem, OBJECT(phb),
|
|
&elroy_config_addr_ops, DEVICE(s),
|
|
"pci-conf-idx", 8);
|
|
memory_region_init_io(&phb->data_mem, OBJECT(phb),
|
|
&elroy_config_data_ops, DEVICE(s),
|
|
"pci-conf-data", 8);
|
|
memory_region_add_subregion(&s->this_mem, 0x40,
|
|
&phb->conf_mem);
|
|
memory_region_add_subregion(&s->this_mem, 0x48,
|
|
&phb->data_mem);
|
|
|
|
/* Elroy PCI bus memory. */
|
|
memory_region_init(&s->pci_mmio, OBJECT(s), "pci-mmio", UINT64_MAX);
|
|
memory_region_init_io(&s->pci_io, OBJECT(s), &unassigned_io_ops, obj,
|
|
"pci-isa-mmio",
|
|
((uint32_t) IOS_DIST_BASE_SIZE) / ROPES_PER_IOC);
|
|
|
|
phb->bus = pci_register_root_bus(DEVICE(s), "pci",
|
|
elroy_set_irq, elroy_pci_map_irq, s,
|
|
&s->pci_mmio, &s->pci_io,
|
|
PCI_DEVFN(0, 0), ELROY_IRQS, TYPE_PCI_BUS);
|
|
|
|
sysbus_init_mmio(sbd, &s->this_mem);
|
|
|
|
qdev_init_gpio_in(DEVICE(obj), elroy_set_irq, ELROY_IRQS);
|
|
}
|
|
|
|
static Property elroy_pcihost_properties[] = {
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static const VMStateDescription vmstate_elroy = {
|
|
.name = "Elroy",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT64(hpa, ElroyState),
|
|
VMSTATE_UINT32(pci_bus_num, ElroyState),
|
|
VMSTATE_UINT64(config_address, ElroyState),
|
|
VMSTATE_UINT64(config_reg_elroy, ElroyState),
|
|
VMSTATE_UINT64(status_control, ElroyState),
|
|
VMSTATE_UINT64(arb_mask, ElroyState),
|
|
VMSTATE_UINT64_ARRAY(mmio_base, ElroyState, (0x0250 - 0x200) / 8),
|
|
VMSTATE_UINT64(error_config, ElroyState),
|
|
VMSTATE_UINT32(iosapic_reg_select, ElroyState),
|
|
VMSTATE_UINT64_ARRAY(iosapic_reg, ElroyState, 0x20),
|
|
VMSTATE_UINT32(ilr, ElroyState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void elroy_pcihost_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->reset = elroy_reset;
|
|
device_class_set_props(dc, elroy_pcihost_properties);
|
|
dc->vmsd = &vmstate_elroy;
|
|
dc->user_creatable = false;
|
|
}
|
|
|
|
static const TypeInfo elroy_pcihost_info = {
|
|
.name = TYPE_ELROY_PCI_HOST_BRIDGE,
|
|
.parent = TYPE_PCI_HOST_BRIDGE,
|
|
.instance_init = elroy_pcihost_init,
|
|
.instance_size = sizeof(ElroyState),
|
|
.class_init = elroy_pcihost_class_init,
|
|
};
|
|
|
|
static void elroy_register_types(void)
|
|
{
|
|
type_register_static(&elroy_pcihost_info);
|
|
}
|
|
|
|
type_init(elroy_register_types)
|
|
|
|
|
|
static ElroyState *elroy_init(int num)
|
|
{
|
|
DeviceState *dev;
|
|
|
|
dev = qdev_new(TYPE_ELROY_PCI_HOST_BRIDGE);
|
|
dev->id = g_strdup_printf("elroy%d", num);
|
|
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
|
|
|
return ELROY_PCI_HOST_BRIDGE(dev);
|
|
}
|
|
|
|
/*
|
|
* Astro Runway chip.
|
|
*/
|
|
|
|
static MemTxResult astro_chip_read_with_attrs(void *opaque, hwaddr addr,
|
|
uint64_t *data, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
AstroState *s = opaque;
|
|
MemTxResult ret = MEMTX_OK;
|
|
uint64_t val = -1;
|
|
int index;
|
|
|
|
switch ((addr >> 3) << 3) {
|
|
/* R2I registers */
|
|
case 0x0000: /* ID */
|
|
val = (0x01 << 3) | 0x01ULL;
|
|
break;
|
|
case 0x0008: /* IOC_CTRL */
|
|
val = s->ioc_ctrl;
|
|
break;
|
|
case 0x0010: /* TOC_CLIENT_ID */
|
|
break;
|
|
case 0x0030: /* HP-UX 10.20 and 11.11 reads it. No idea. */
|
|
val = -1;
|
|
break;
|
|
case 0x0078: /* NetBSD reads 0x78 ? */
|
|
val = -1;
|
|
break;
|
|
case 0x0300 ... 0x03d8: /* LMMIO_DIRECT0_BASE... */
|
|
index = (addr - 0x300) / 8;
|
|
val = s->ioc_ranges[index];
|
|
break;
|
|
case 0x10200:
|
|
val = 0;
|
|
break;
|
|
case 0x10220:
|
|
case 0x10230: /* HP-UX 11.11 reads it. No idea. */
|
|
val = -1;
|
|
break;
|
|
case 0x22108: /* IOC STATUS_CONTROL */
|
|
val = s->ioc_status_ctrl;
|
|
break;
|
|
case 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */
|
|
index = (addr - 0x20200) / 8;
|
|
val = s->ioc_rope_control[index];
|
|
break;
|
|
case 0x20040: /* IOC Rope config */
|
|
val = s->ioc_rope_config;
|
|
break;
|
|
case 0x20050: /* IOC Rope debug */
|
|
val = 0;
|
|
break;
|
|
case 0x20108: /* IOC STATUS_CONTROL */
|
|
val = s->ioc_status_control;
|
|
break;
|
|
case 0x20310: /* IOC_PCOM */
|
|
val = s->tlb_pcom;
|
|
/* TODO: flush iommu */
|
|
break;
|
|
case 0x20400:
|
|
val = s->ioc_flush_control;
|
|
break;
|
|
/* empty placeholders for non-existent elroys */
|
|
#define EMPTY_PORT(x) case x: case x+8: val = 0; break; \
|
|
case x+40: case x+48: val = UINT64_MAX; break;
|
|
EMPTY_PORT(0x30000)
|
|
EMPTY_PORT(0x32000)
|
|
EMPTY_PORT(0x34000)
|
|
EMPTY_PORT(0x36000)
|
|
EMPTY_PORT(0x38000)
|
|
EMPTY_PORT(0x3a000)
|
|
EMPTY_PORT(0x3c000)
|
|
EMPTY_PORT(0x3e000)
|
|
#undef EMPTY_PORT
|
|
|
|
default:
|
|
trace_astro_chip_read(addr, size, val);
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
/* for 32-bit accesses mask return value */
|
|
val = mask_32bit_val(addr, size, val);
|
|
|
|
trace_astro_chip_read(addr, size, val);
|
|
*data = val;
|
|
return ret;
|
|
}
|
|
|
|
static MemTxResult astro_chip_write_with_attrs(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
AstroState *s = opaque;
|
|
|
|
trace_astro_chip_write(addr, size, val);
|
|
|
|
switch ((addr >> 3) << 3) {
|
|
case 0x0000: /* ID */
|
|
break;
|
|
case 0x0008: /* IOC_CTRL */
|
|
val &= 0x0ffffff;
|
|
put_val_in_int64(&s->ioc_ctrl, addr, size, val);
|
|
break;
|
|
case 0x0010: /* TOC_CLIENT_ID */
|
|
break;
|
|
case 0x0030: /* HP-UX 10.20 and 11.11 reads it. No idea. */
|
|
break;
|
|
case 0x0300 ... 0x03d8 - 1: /* LMMIO_DIRECT0_BASE... */
|
|
put_val_in_arrary(s->ioc_ranges, 0x300, addr, size, val);
|
|
break;
|
|
case 0x10200:
|
|
case 0x10220:
|
|
case 0x10230: /* HP-UX 11.11 reads it. No idea. */
|
|
break;
|
|
case 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */
|
|
put_val_in_arrary(s->ioc_rope_control, 0x20200, addr, size, val);
|
|
break;
|
|
case 0x20040: /* IOC Rope config */
|
|
case 0x22040:
|
|
put_val_in_int64(&s->ioc_rope_config, addr, size, val);
|
|
break;
|
|
case 0x20300:
|
|
case 0x22300:
|
|
put_val_in_int64(&s->tlb_ibase, addr, size, val);
|
|
break;
|
|
case 0x20308:
|
|
case 0x22308:
|
|
put_val_in_int64(&s->tlb_imask, addr, size, val);
|
|
break;
|
|
case 0x20310:
|
|
case 0x22310:
|
|
put_val_in_int64(&s->tlb_pcom, addr, size, val);
|
|
/* TODO: flush iommu */
|
|
break;
|
|
case 0x20318:
|
|
case 0x22318:
|
|
put_val_in_int64(&s->tlb_tcnfg, addr, size, val);
|
|
break;
|
|
case 0x20320:
|
|
case 0x22320:
|
|
put_val_in_int64(&s->tlb_pdir_base, addr, size, val);
|
|
break;
|
|
case 0x22000: /* func_id */
|
|
break;
|
|
case 0x22008: /* func_class */
|
|
break;
|
|
case 0x22050: /* rope_debug */
|
|
break;
|
|
case 0x22108: /* IOC STATUS_CONTROL */
|
|
put_val_in_int64(&s->ioc_status_ctrl, addr, size, val);
|
|
break;
|
|
/*
|
|
* empty placeholders for non-existent elroys, e.g.
|
|
* func_class, pci config & data
|
|
*/
|
|
#define EMPTY_PORT(x) case x: case x+8: case x+0x40: case x+0x48:
|
|
EMPTY_PORT(0x30000)
|
|
EMPTY_PORT(0x32000)
|
|
EMPTY_PORT(0x34000)
|
|
EMPTY_PORT(0x36000)
|
|
EMPTY_PORT(0x38000)
|
|
EMPTY_PORT(0x3a000)
|
|
EMPTY_PORT(0x3c000)
|
|
EMPTY_PORT(0x3e000)
|
|
break;
|
|
#undef EMPTY_PORT
|
|
|
|
default:
|
|
/* Controlled by astro_chip_mem_valid above. */
|
|
trace_astro_chip_write(addr, size, val);
|
|
g_assert_not_reached();
|
|
}
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static const MemoryRegionOps astro_chip_ops = {
|
|
.read_with_attrs = astro_chip_read_with_attrs,
|
|
.write_with_attrs = astro_chip_write_with_attrs,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
};
|
|
|
|
static const VMStateDescription vmstate_astro = {
|
|
.name = "Astro",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT64(ioc_ctrl, AstroState),
|
|
VMSTATE_UINT64(ioc_status_ctrl, AstroState),
|
|
VMSTATE_UINT64_ARRAY(ioc_ranges, AstroState, (0x03d8 - 0x300) / 8),
|
|
VMSTATE_UINT64(ioc_rope_config, AstroState),
|
|
VMSTATE_UINT64(ioc_status_control, AstroState),
|
|
VMSTATE_UINT64(ioc_flush_control, AstroState),
|
|
VMSTATE_UINT64_ARRAY(ioc_rope_control, AstroState, 8),
|
|
VMSTATE_UINT64(tlb_ibase, AstroState),
|
|
VMSTATE_UINT64(tlb_imask, AstroState),
|
|
VMSTATE_UINT64(tlb_pcom, AstroState),
|
|
VMSTATE_UINT64(tlb_tcnfg, AstroState),
|
|
VMSTATE_UINT64(tlb_pdir_base, AstroState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void astro_reset(DeviceState *dev)
|
|
{
|
|
AstroState *s = ASTRO_CHIP(dev);
|
|
int i;
|
|
|
|
s->ioc_ctrl = 0x29cf;
|
|
s->ioc_rope_config = 0xc5f;
|
|
s->ioc_flush_control = 0xb03;
|
|
s->ioc_status_control = 0;
|
|
memset(&s->ioc_rope_control, 0, sizeof(s->ioc_rope_control));
|
|
|
|
/*
|
|
* The SBA BASE/MASK registers control CPU -> IO routing.
|
|
* The LBA BASE/MASK registers control IO -> System routing (in Elroy)
|
|
*/
|
|
memset(&s->ioc_ranges, 0, sizeof(s->ioc_ranges));
|
|
s->ioc_ranges[(0x360 - 0x300) / 8] = LMMIO_DIST_BASE_ADDR | 0x01; /* LMMIO_DIST_BASE (SBA) */
|
|
s->ioc_ranges[(0x368 - 0x300) / 8] = 0xfc000000; /* LMMIO_DIST_MASK */
|
|
s->ioc_ranges[(0x370 - 0x300) / 8] = 0; /* LMMIO_DIST_ROUTE */
|
|
s->ioc_ranges[(0x390 - 0x300) / 8] = IOS_DIST_BASE_ADDR | 0x01; /* IOS_DIST_BASE */
|
|
s->ioc_ranges[(0x398 - 0x300) / 8] = 0xffffff0000; /* IOS_DIST_MASK */
|
|
s->ioc_ranges[(0x3a0 - 0x300) / 8] = 0x3400000000000000ULL; /* IOS_DIST_ROUTE */
|
|
s->ioc_ranges[(0x3c0 - 0x300) / 8] = 0xfffee00000; /* IOS_DIRECT_BASE */
|
|
s->ioc_ranges[(0x3c8 - 0x300) / 8] = 0xffffff0000; /* IOS_DIRECT_MASK */
|
|
s->ioc_ranges[(0x3d0 - 0x300) / 8] = 0x0; /* IOS_DIRECT_ROUTE */
|
|
|
|
s->tlb_ibase = 0;
|
|
s->tlb_imask = 0;
|
|
s->tlb_pcom = 0;
|
|
s->tlb_tcnfg = 0;
|
|
s->tlb_pdir_base = 0;
|
|
|
|
for (i = 0; i < ELROY_NUM; i++) {
|
|
elroy_reset(DEVICE(s->elroy[i]));
|
|
}
|
|
}
|
|
|
|
static void astro_init(Object *obj)
|
|
{
|
|
}
|
|
|
|
static void astro_realize(DeviceState *obj, Error **errp)
|
|
{
|
|
AstroState *s = ASTRO_CHIP(obj);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
int i;
|
|
|
|
memory_region_init_io(&s->this_mem, OBJECT(s), &astro_chip_ops,
|
|
s, "astro", 0x40000);
|
|
sysbus_init_mmio(sbd, &s->this_mem);
|
|
|
|
/* Host memory as seen from Elroys PCI side, via the IOMMU. */
|
|
memory_region_init_iommu(&s->iommu, sizeof(s->iommu),
|
|
TYPE_ASTRO_IOMMU_MEMORY_REGION, OBJECT(s),
|
|
"iommu-astro", UINT64_MAX);
|
|
address_space_init(&s->iommu_as, MEMORY_REGION(&s->iommu),
|
|
"bm-pci");
|
|
|
|
/* Create Elroys (PCI host bus chips). */
|
|
for (i = 0; i < ELROY_NUM; i++) {
|
|
static const int elroy_hpa_offsets[ELROY_NUM] = {
|
|
0x30000, 0x32000, 0x38000, 0x3c000 };
|
|
static const char elroy_rope_nr[ELROY_NUM] = {
|
|
0, 1, 4, 6 }; /* busnum path, e.g. [10:6] */
|
|
int addr_offset;
|
|
ElroyState *elroy;
|
|
hwaddr map_addr;
|
|
uint64_t map_size;
|
|
int rope;
|
|
|
|
addr_offset = elroy_hpa_offsets[i];
|
|
rope = elroy_rope_nr[i];
|
|
|
|
elroy = elroy_init(i);
|
|
s->elroy[i] = elroy;
|
|
elroy->hpa = ASTRO_HPA + addr_offset;
|
|
elroy->pci_bus_num = i;
|
|
elroy->astro = s;
|
|
|
|
/*
|
|
* NOTE: we only allow PCI devices on first Elroy for now.
|
|
* SeaBIOS will not find devices on the other busses.
|
|
*/
|
|
if (i > 0) {
|
|
qbus_mark_full(&PCI_HOST_BRIDGE(elroy)->bus->qbus);
|
|
}
|
|
|
|
/* map elroy config addresses into Astro space */
|
|
memory_region_add_subregion(&s->this_mem, addr_offset,
|
|
&elroy->this_mem);
|
|
|
|
/* LMMIO */
|
|
elroy->mmio_base[(0x0200 - 0x200) / 8] = 0xf0000001;
|
|
elroy->mmio_base[(0x0208 - 0x200) / 8] = 0xf8000000;
|
|
/* GMMIO */
|
|
elroy->mmio_base[(0x0210 - 0x200) / 8] = 0x000000f800000001;
|
|
elroy->mmio_base[(0x0218 - 0x200) / 8] = 0x000000ff80000000;
|
|
/* WLMMIO */
|
|
elroy->mmio_base[(0x0220 - 0x200) / 8] = 0xf0000001;
|
|
elroy->mmio_base[(0x0228 - 0x200) / 8] = 0xf0000000;
|
|
/* WGMMIO */
|
|
elroy->mmio_base[(0x0230 - 0x200) / 8] = 0x000000f800000001;
|
|
elroy->mmio_base[(0x0238 - 0x200) / 8] = 0x000000fc00000000;
|
|
/* IOS_BASE */
|
|
map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC;
|
|
elroy->mmio_base[(0x0240 - 0x200) / 8] = rope * map_size | 0x01;
|
|
elroy->mmio_base[(0x0248 - 0x200) / 8] = 0x0000e000;
|
|
|
|
/* map elroys mmio */
|
|
map_size = LMMIO_DIST_BASE_SIZE / ROPES_PER_IOC;
|
|
map_addr = F_EXTEND(LMMIO_DIST_BASE_ADDR + rope * map_size);
|
|
memory_region_init_alias(&elroy->pci_mmio_alias, OBJECT(elroy),
|
|
"pci-mmio-alias",
|
|
&elroy->pci_mmio, (uint32_t) map_addr, map_size);
|
|
memory_region_add_subregion(get_system_memory(), map_addr,
|
|
&elroy->pci_mmio_alias);
|
|
|
|
/* map elroys io */
|
|
map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC;
|
|
map_addr = F_EXTEND(IOS_DIST_BASE_ADDR + rope * map_size);
|
|
memory_region_add_subregion(get_system_memory(), map_addr,
|
|
&elroy->pci_io);
|
|
|
|
/* Host memory as seen from the PCI side, via the IOMMU. */
|
|
pci_setup_iommu(PCI_HOST_BRIDGE(elroy)->bus, &elroy_pcihost_iommu_ops,
|
|
elroy);
|
|
}
|
|
}
|
|
|
|
static void astro_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->reset = astro_reset;
|
|
dc->vmsd = &vmstate_astro;
|
|
dc->realize = astro_realize;
|
|
/*
|
|
* astro with elroys are hard part of the newer PA2.0 machines and can not
|
|
* be created without that hardware
|
|
*/
|
|
dc->user_creatable = false;
|
|
}
|
|
|
|
static const TypeInfo astro_chip_info = {
|
|
.name = TYPE_ASTRO_CHIP,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_init = astro_init,
|
|
.instance_size = sizeof(AstroState),
|
|
.class_init = astro_class_init,
|
|
};
|
|
|
|
static void astro_iommu_memory_region_class_init(ObjectClass *klass,
|
|
void *data)
|
|
{
|
|
IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
|
|
|
|
imrc->translate = astro_translate_iommu;
|
|
}
|
|
|
|
static const TypeInfo astro_iommu_memory_region_info = {
|
|
.parent = TYPE_IOMMU_MEMORY_REGION,
|
|
.name = TYPE_ASTRO_IOMMU_MEMORY_REGION,
|
|
.class_init = astro_iommu_memory_region_class_init,
|
|
};
|
|
|
|
|
|
static void astro_register_types(void)
|
|
{
|
|
type_register_static(&astro_chip_info);
|
|
type_register_static(&astro_iommu_memory_region_info);
|
|
}
|
|
|
|
type_init(astro_register_types)
|