* PCIe Root Complex (RC)
*/
+#define ASPEED_PCIE_CFG_RC_MAX_MSI 64
+
static void aspeed_pcie_rc_set_irq(void *opaque, int irq, int level)
{
AspeedPCIERcState *rc = (AspeedPCIERcState *) opaque;
return irq_num % PCI_NUM_PINS;
}
+static void aspeed_pcie_rc_msi_notify(AspeedPCIERcState *rc, uint64_t data)
+{
+ AspeedPCIECfgState *cfg =
+ container_of(rc, AspeedPCIECfgState, rc);
+ uint32_t reg;
+
+ /* Written data is the HW IRQ number */
+ assert(data < ASPEED_PCIE_CFG_RC_MAX_MSI);
+
+ reg = (data < 32) ?
+ cfg->rc_regs->msi_sts0_reg : cfg->rc_regs->msi_sts1_reg;
+ cfg->regs[reg] |= BIT(data % 32);
+
+ trace_aspeed_pcie_rc_msi_set_irq(cfg->id, data, 1);
+ qemu_set_irq(rc->irq, 1);
+}
+
+static void aspeed_pcie_rc_msi_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ AspeedPCIERcState *rc = ASPEED_PCIE_RC(opaque);
+ AspeedPCIECfgState *cfg =
+ container_of(rc, AspeedPCIECfgState, rc);
+
+ trace_aspeed_pcie_rc_msi_notify(cfg->id, addr + rc->msi_addr, data);
+ aspeed_pcie_rc_msi_notify(rc, data);
+}
+
+static const MemoryRegionOps aspeed_pcie_rc_msi_ops = {
+ .write = aspeed_pcie_rc_msi_write,
+ .read = NULL,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static AddressSpace *aspeed_pcie_rc_get_as(PCIBus *bus, void *opaque, int devfn)
+{
+ AspeedPCIERcState *rc = ASPEED_PCIE_RC(opaque);
+ return &rc->iommu_as;
+}
+
+static const PCIIOMMUOps aspeed_pcie_rc_iommu_ops = {
+ .get_address_space = aspeed_pcie_rc_get_as,
+};
+
static void aspeed_pcie_rc_realize(DeviceState *dev, Error **errp)
{
PCIExpressHost *pex = PCIE_HOST_BRIDGE(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
g_autofree char *ioport_window_name = NULL;
g_autofree char *mmio_window_name = NULL;
+ g_autofree char *iommu_root_name = NULL;
+ g_autofree char *dram_alias_name = NULL;
g_autofree char *root_bus_name = NULL;
/* PCI configuration space */
&rc->io, 0, 4, TYPE_PCIE_BUS);
pci->bus->flags |= PCI_BUS_EXTENDED_CONFIG_SPACE;
+ /*
+ * PCIe memory view setup
+ *
+ * Background:
+ * - On AST2700, all Root Complexes use the same MSI address. This MSI
+ * address is not normal system RAM - it is a PCI system memory address.
+ * If we map the MSI/MSI-X window into real system memory, a write from
+ * one EP can be seen by all RCs and wrongly trigger interrupts on them.
+ *
+ * Design:
+ * - MSI/MSI-X here is just a placeholder address so RC and EP can talk.
+ * We make a separate MMIO space (iommu_root) for the MSI window so the
+ * writes stay local to each RC.
+ *
+ * DMA:
+ * - EPs still need access to real system memory for DMA. We add a DRAM
+ * alias in the PCI space so DMA works as expected.
+ */
+ iommu_root_name = g_strdup_printf("pcie.%d.iommu_root", cfg->id);
+ memory_region_init(&rc->iommu_root, OBJECT(rc), iommu_root_name,
+ UINT64_MAX);
+ address_space_init(&rc->iommu_as, &rc->iommu_root, iommu_root_name);
+ /* setup MSI */
+ memory_region_init_io(&rc->msi_window, OBJECT(rc),
+ &aspeed_pcie_rc_msi_ops, rc,
+ "msi_window", 4);
+ memory_region_add_subregion(&rc->iommu_root, rc->msi_addr,
+ &rc->msi_window);
+ /* setup DRAM for DMA */
+ assert(rc->dram_mr != NULL);
+ dram_alias_name = g_strdup_printf("pcie.%d.dram_alias", cfg->id);
+ memory_region_init_alias(&rc->dram_alias, OBJECT(rc), dram_alias_name,
+ rc->dram_mr, 0, memory_region_size(rc->dram_mr));
+ memory_region_add_subregion(&rc->iommu_root, rc->dram_base,
+ &rc->dram_alias);
+ pci_setup_iommu(pci->bus, &aspeed_pcie_rc_iommu_ops, rc);
+
/* setup root device */
if (rc->has_rd) {
object_initialize_child(OBJECT(rc), "root_device", &rc->root_device,
DEFINE_PROP_UINT32("bus-nr", AspeedPCIERcState, bus_nr, 0),
DEFINE_PROP_BOOL("has-rd", AspeedPCIERcState, has_rd, 0),
DEFINE_PROP_UINT32("rp-addr", AspeedPCIERcState, rp_addr, 0),
+ DEFINE_PROP_UINT32("msi-addr", AspeedPCIERcState, msi_addr, 0),
+ DEFINE_PROP_UINT64("dram-base", AspeedPCIERcState, dram_base, 0),
+ DEFINE_PROP_LINK("dram", AspeedPCIERcState, dram_mr, TYPE_MEMORY_REGION,
+ MemoryRegion *),
};
static void aspeed_pcie_rc_class_init(ObjectClass *klass, const void *data)
SHARED_FIELD(H2X_RC_INT_INTDONE, 4, 1)
SHARED_FIELD(H2X_RC_INT_INTX, 0, 4)
REG32(H2X_RC_H_RDATA, 0xCC)
+REG32(H2X_RC_H_MSI_EN0, 0xE0)
+REG32(H2X_RC_H_MSI_EN1, 0xE4)
+REG32(H2X_RC_H_MSI_STS0, 0xE8)
+REG32(H2X_RC_H_MSI_STS1, 0xEC)
#define TLP_FMTTYPE_CFGRD0 0x04 /* Configuration Read Type 0 */
#define TLP_FMTTYPE_CFGWR0 0x44 /* Configuration Write Type 0 */
.rc = {
.int_en_reg = R_H2X_RC_H_INT_EN,
.int_sts_reg = R_H2X_RC_H_INT_STS,
+ .msi_sts0_reg = R_H2X_RC_H_MSI_STS0,
+ .msi_sts1_reg = R_H2X_RC_H_MSI_STS1,
},
};
}
s->regs[reg] &= ~data | H2X_RC_INT_INTX_MASK;
break;
+ /*
+ * These status registers are used for notify sources ISR are executed.
+ * If one source ISR is executed, it will clear one bit.
+ * If it clear all bits, it means to initialize this register status
+ * rather than sources ISR are executed.
+ */
+ case R_H2X_RC_H_MSI_STS0:
+ case R_H2X_RC_H_MSI_STS1:
+ if (data == 0) {
+ return ;
+ }
+
+ s->regs[reg] &= ~data;
+ if (data == 0xffffffff) {
+ return;
+ }
+
+ if (!s->regs[R_H2X_RC_H_MSI_STS0] &&
+ !s->regs[R_H2X_RC_H_MSI_STS1]) {
+ trace_aspeed_pcie_rc_msi_clear_irq(s->id, 0);
+ qemu_set_irq(s->rc.irq, 0);
+ }
+ break;
default:
s->regs[reg] = data;
break;
AspeedPCIECfgState *s = ASPEED_PCIE_CFG(obj);
object_initialize_child(obj, "rc", &s->rc, TYPE_ASPEED_PCIE_RC);
+ object_property_add_alias(obj, "dram", OBJECT(&s->rc), "dram");
+ object_property_add_alias(obj, "dram-base", OBJECT(&s->rc), "dram-base");
return;
}
object_property_set_int(OBJECT(&s->rc), "rp-addr",
apc->rc_rp_addr,
&error_abort);
+ object_property_set_int(OBJECT(&s->rc), "msi-addr",
+ apc->rc_msi_addr,
+ &error_abort);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->rc), errp)) {
return;
}
apc->reg_ops = &aspeed_pcie_cfg_ops;
apc->reg_map = &aspeed_regmap;
apc->nr_regs = 0x100 >> 2;
+ apc->rc_msi_addr = 0x1e77005C;
apc->rc_bus_nr = 0x80;
apc->rc_has_rd = true;
apc->rc_rp_addr = PCI_DEVFN(8, 0);