hw/pci-host: Add Astro system bus adapter found on PA-RISC machines
The 64-bit PA-RISC machines use a Astro system bus adapter (SBA) with Elroy PCI host chips. Later generation Astro chips were named Pluto, Ike and REO. Signed-off-by: Helge Deller <deller@gmx.de>
This commit is contained in:
parent
a1e6a5c462
commit
e029bb00a7
885
hw/pci-host/astro.c
Normal file
885
hw/pci-host/astro.c
Normal file
@ -0,0 +1,885 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#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 "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 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 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,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* A subroutine of astro_translate_iommu that builds an IOMMUTLBEntry using the
|
||||
* given translated address and mask.
|
||||
*/
|
||||
static bool make_iommu_tlbe(hwaddr addr, hwaddr taddr, hwaddr mask,
|
||||
IOMMUTLBEntry *ret)
|
||||
{
|
||||
hwaddr tce_mask = ~((1ull << 12) - 1);
|
||||
ret->target_as = &address_space_memory;
|
||||
ret->iova = addr & tce_mask;
|
||||
ret->translated_addr = taddr & tce_mask;
|
||||
ret->addr_mask = ~tce_mask;
|
||||
ret->perm = IOMMU_RW;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
IOMMUTLBEntry ret = {
|
||||
.target_as = &address_space_memory,
|
||||
.iova = addr,
|
||||
.translated_addr = 0,
|
||||
.addr_mask = ~(hwaddr)0,
|
||||
.perm = IOMMU_NONE,
|
||||
};
|
||||
hwaddr pdir_ptr, index, a, 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 IOVP_MASK PAGE_MASK
|
||||
#define SBA_PDIR_VALID_BIT 0x8000000000000000ULL
|
||||
|
||||
/* "range enable" flag cleared? */
|
||||
if ((s->tlb_ibase & 1) == 0) {
|
||||
make_iommu_tlbe(addr, addr, addr_mask, &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
a = addr;
|
||||
ibase = s->tlb_ibase & ~1ULL;
|
||||
if ((a & s->tlb_imask) != ibase) {
|
||||
/* do not translate this one! */
|
||||
make_iommu_tlbe(addr, addr, addr_mask, &ret);
|
||||
return ret;
|
||||
}
|
||||
index = PDIR_INDEX(a);
|
||||
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 ? */
|
||||
g_assert_not_reached();
|
||||
goto failure;
|
||||
}
|
||||
entry &= ~SBA_PDIR_VALID_BIT;
|
||||
entry >>= IOVP_SHIFT;
|
||||
entry <<= 12;
|
||||
entry |= addr & 0xfff;
|
||||
make_iommu_tlbe(addr, entry, addr_mask, &ret);
|
||||
goto success;
|
||||
|
||||
failure:
|
||||
ret = (IOMMUTLBEntry) { .perm = IOMMU_NONE };
|
||||
success:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static AddressSpace *elroy_pcihost_set_iommu(PCIBus *bus, void *opaque,
|
||||
int devfn)
|
||||
{
|
||||
ElroyState *s = opaque;
|
||||
return &s->astro->iommu_as;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, 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 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 0x22108: /* IOC STATUS_CONTROL */
|
||||
put_val_in_int64(&s->ioc_status_ctrl, addr, size, val);
|
||||
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 */
|
||||
put_val_in_int64(&s->ioc_rope_config, addr, size, val);
|
||||
break;
|
||||
case 0x20300:
|
||||
put_val_in_int64(&s->tlb_ibase, addr, size, val);
|
||||
break;
|
||||
case 0x20308:
|
||||
put_val_in_int64(&s->tlb_imask, addr, size, val);
|
||||
break;
|
||||
case 0x20310:
|
||||
put_val_in_int64(&s->tlb_pcom, addr, size, val);
|
||||
/* TODO: flush iommu */
|
||||
break;
|
||||
case 0x20318:
|
||||
put_val_in_int64(&s->tlb_tcnfg, addr, size, val);
|
||||
break;
|
||||
case 0x20320:
|
||||
put_val_in_int64(&s->tlb_pdir_base, 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 = (uint32_t) (LMMIO_DIST_BASE_ADDR + rope * map_size);
|
||||
memory_region_init_alias(&elroy->pci_mmio_alias, OBJECT(elroy),
|
||||
"pci-mmio-alias",
|
||||
&elroy->pci_mmio, map_addr, map_size);
|
||||
memory_region_add_subregion(get_system_memory(), map_addr,
|
||||
&elroy->pci_mmio_alias);
|
||||
|
||||
map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC;
|
||||
map_addr = (uint32_t) (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_set_iommu,
|
||||
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)
|
@ -46,3 +46,14 @@ pnv_phb4_xive_notify_abt(uint64_t notif_port, uint64_t data) "notif=@0x%"PRIx64"
|
||||
dino_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d"
|
||||
dino_chip_read(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x"
|
||||
dino_chip_write(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x"
|
||||
|
||||
# astro.c
|
||||
astro_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d"
|
||||
astro_chip_read(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
astro_chip_write(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
elroy_read(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
elroy_write(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
elroy_pci_config_data_read(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
elroy_pci_config_data_write(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
iosapic_reg_write(uint64_t reg_select, int size, uint64_t val) "reg_select 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
iosapic_reg_read(uint64_t reg_select, int size, uint64_t val) "reg_select 0x%"PRIx64" size %d val 0x%"PRIx64
|
||||
|
92
include/hw/pci-host/astro.h
Normal file
92
include/hw/pci-host/astro.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* HP-PARISC Astro Bus connector with Elroy PCI host bridges
|
||||
*/
|
||||
|
||||
#ifndef ASTRO_H
|
||||
#define ASTRO_H
|
||||
|
||||
#include "hw/pci/pci_host.h"
|
||||
|
||||
#define ASTRO_HPA 0xfed00000
|
||||
|
||||
#define ROPES_PER_IOC 8 /* per Ike half or Pluto/Astro */
|
||||
|
||||
#define TYPE_ASTRO_CHIP "astro-chip"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(AstroState, ASTRO_CHIP)
|
||||
|
||||
#define TYPE_ELROY_PCI_HOST_BRIDGE "elroy-pcihost"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(ElroyState, ELROY_PCI_HOST_BRIDGE)
|
||||
|
||||
#define ELROY_NUM 4 /* # of Elroys */
|
||||
#define ELROY_IRQS 8 /* IOSAPIC IRQs */
|
||||
|
||||
/* ASTRO Memory and I/O regions */
|
||||
#define LMMIO_DIST_BASE_ADDR 0xf4000000ULL
|
||||
#define LMMIO_DIST_BASE_SIZE 0x4000000ULL
|
||||
|
||||
#define IOS_DIST_BASE_ADDR 0xfffee00000ULL
|
||||
#define IOS_DIST_BASE_SIZE 0x10000ULL
|
||||
|
||||
struct AstroState;
|
||||
|
||||
struct ElroyState {
|
||||
PCIHostState parent_obj;
|
||||
|
||||
/* parent Astro device */
|
||||
struct AstroState *astro;
|
||||
|
||||
/* HPA of this Elroy */
|
||||
hwaddr hpa;
|
||||
|
||||
/* PCI bus number (Elroy number) */
|
||||
unsigned int pci_bus_num;
|
||||
|
||||
uint64_t config_address;
|
||||
uint64_t config_reg_elroy;
|
||||
|
||||
uint64_t status_control;
|
||||
uint64_t arb_mask;
|
||||
uint64_t mmio_base[(0x0250 - 0x200) / 8];
|
||||
uint64_t error_config;
|
||||
|
||||
uint32_t iosapic_reg_select;
|
||||
uint64_t iosapic_reg[0x20];
|
||||
|
||||
uint32_t ilr;
|
||||
|
||||
MemoryRegion this_mem;
|
||||
|
||||
MemoryRegion pci_mmio;
|
||||
MemoryRegion pci_mmio_alias;
|
||||
MemoryRegion pci_hole;
|
||||
MemoryRegion pci_io;
|
||||
};
|
||||
|
||||
struct AstroState {
|
||||
PCIHostState parent_obj;
|
||||
|
||||
uint64_t ioc_ctrl;
|
||||
uint64_t ioc_status_ctrl;
|
||||
uint64_t ioc_ranges[(0x03d8 - 0x300) / 8];
|
||||
uint64_t ioc_rope_config;
|
||||
uint64_t ioc_status_control;
|
||||
uint64_t ioc_flush_control;
|
||||
uint64_t ioc_rope_control[8];
|
||||
uint64_t tlb_ibase;
|
||||
uint64_t tlb_imask;
|
||||
uint64_t tlb_pcom;
|
||||
uint64_t tlb_tcnfg;
|
||||
uint64_t tlb_pdir_base;
|
||||
|
||||
struct ElroyState *elroy[ELROY_NUM];
|
||||
|
||||
MemoryRegion this_mem;
|
||||
|
||||
MemoryRegion pci_mmio;
|
||||
MemoryRegion pci_io;
|
||||
|
||||
IOMMUMemoryRegion iommu;
|
||||
AddressSpace iommu_as;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user