Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pci-bridge/cxl_upstream: Add a CXL switch upstream port
An initial simple upstream port emulation to allow the creation of CXL switches. The Device ID has been allocated for this use. Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Message-Id: <20220616145126.8002-2-Jonathan.Cameron@huawei.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
- Loading branch information
Showing
3 changed files
with
222 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
/* | ||
* Emulated CXL Switch Upstream Port | ||
* | ||
* Copyright (c) 2022 Huawei Technologies. | ||
* | ||
* Based on xio3130_upstream.c | ||
* | ||
* SPDX-License-Identifier: GPL-2.0-or-later | ||
*/ | ||
|
||
#include "qemu/osdep.h" | ||
#include "qemu/log.h" | ||
#include "hw/pci/msi.h" | ||
#include "hw/pci/pcie.h" | ||
#include "hw/pci/pcie_port.h" | ||
|
||
#define CXL_UPSTREAM_PORT_MSI_NR_VECTOR 1 | ||
|
||
#define CXL_UPSTREAM_PORT_MSI_OFFSET 0x70 | ||
#define CXL_UPSTREAM_PORT_PCIE_CAP_OFFSET 0x90 | ||
#define CXL_UPSTREAM_PORT_AER_OFFSET 0x100 | ||
#define CXL_UPSTREAM_PORT_DVSEC_OFFSET \ | ||
(CXL_UPSTREAM_PORT_AER_OFFSET + PCI_ERR_SIZEOF) | ||
|
||
typedef struct CXLUpstreamPort { | ||
/*< private >*/ | ||
PCIEPort parent_obj; | ||
|
||
/*< public >*/ | ||
CXLComponentState cxl_cstate; | ||
} CXLUpstreamPort; | ||
|
||
CXLComponentState *cxl_usp_to_cstate(CXLUpstreamPort *usp) | ||
{ | ||
return &usp->cxl_cstate; | ||
} | ||
|
||
static void cxl_usp_dvsec_write_config(PCIDevice *dev, uint32_t addr, | ||
uint32_t val, int len) | ||
{ | ||
CXLUpstreamPort *usp = CXL_USP(dev); | ||
|
||
if (range_contains(&usp->cxl_cstate.dvsecs[EXTENSIONS_PORT_DVSEC], addr)) { | ||
uint8_t *reg = &dev->config[addr]; | ||
addr -= usp->cxl_cstate.dvsecs[EXTENSIONS_PORT_DVSEC].lob; | ||
if (addr == PORT_CONTROL_OFFSET) { | ||
if (pci_get_word(reg) & PORT_CONTROL_UNMASK_SBR) { | ||
/* unmask SBR */ | ||
qemu_log_mask(LOG_UNIMP, "SBR mask control is not supported\n"); | ||
} | ||
if (pci_get_word(reg) & PORT_CONTROL_ALT_MEMID_EN) { | ||
/* Alt Memory & ID Space Enable */ | ||
qemu_log_mask(LOG_UNIMP, | ||
"Alt Memory & ID space is not supported\n"); | ||
} | ||
} | ||
} | ||
} | ||
|
||
static void cxl_usp_write_config(PCIDevice *d, uint32_t address, | ||
uint32_t val, int len) | ||
{ | ||
pci_bridge_write_config(d, address, val, len); | ||
pcie_cap_flr_write_config(d, address, val, len); | ||
pcie_aer_write_config(d, address, val, len); | ||
|
||
cxl_usp_dvsec_write_config(d, address, val, len); | ||
} | ||
|
||
static void latch_registers(CXLUpstreamPort *usp) | ||
{ | ||
uint32_t *reg_state = usp->cxl_cstate.crb.cache_mem_registers; | ||
uint32_t *write_msk = usp->cxl_cstate.crb.cache_mem_regs_write_mask; | ||
|
||
cxl_component_register_init_common(reg_state, write_msk, | ||
CXL2_UPSTREAM_PORT); | ||
ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, TARGET_COUNT, 8); | ||
} | ||
|
||
static void cxl_usp_reset(DeviceState *qdev) | ||
{ | ||
PCIDevice *d = PCI_DEVICE(qdev); | ||
CXLUpstreamPort *usp = CXL_USP(qdev); | ||
|
||
pci_bridge_reset(qdev); | ||
pcie_cap_deverr_reset(d); | ||
latch_registers(usp); | ||
} | ||
|
||
static void build_dvsecs(CXLComponentState *cxl) | ||
{ | ||
uint8_t *dvsec; | ||
|
||
dvsec = (uint8_t *)&(CXLDVSECPortExtensions){ | ||
.status = 0x1, /* Port Power Management Init Complete */ | ||
}; | ||
cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT, | ||
EXTENSIONS_PORT_DVSEC_LENGTH, | ||
EXTENSIONS_PORT_DVSEC, | ||
EXTENSIONS_PORT_DVSEC_REVID, dvsec); | ||
dvsec = (uint8_t *)&(CXLDVSECPortFlexBus){ | ||
.cap = 0x27, /* Cache, IO, Mem, non-MLD */ | ||
.ctrl = 0x27, /* Cache, IO, Mem */ | ||
.status = 0x26, /* same */ | ||
.rcvd_mod_ts_data_phase1 = 0xef, /* WTF? */ | ||
}; | ||
cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT, | ||
PCIE_FLEXBUS_PORT_DVSEC_LENGTH_2_0, | ||
PCIE_FLEXBUS_PORT_DVSEC, | ||
PCIE_FLEXBUS_PORT_DVSEC_REVID_2_0, dvsec); | ||
|
||
dvsec = (uint8_t *)&(CXLDVSECRegisterLocator){ | ||
.rsvd = 0, | ||
.reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX, | ||
.reg0_base_hi = 0, | ||
}; | ||
cxl_component_create_dvsec(cxl, CXL2_UPSTREAM_PORT, | ||
REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC, | ||
REG_LOC_DVSEC_REVID, dvsec); | ||
} | ||
|
||
static void cxl_usp_realize(PCIDevice *d, Error **errp) | ||
{ | ||
PCIEPort *p = PCIE_PORT(d); | ||
CXLUpstreamPort *usp = CXL_USP(d); | ||
CXLComponentState *cxl_cstate = &usp->cxl_cstate; | ||
ComponentRegisters *cregs = &cxl_cstate->crb; | ||
MemoryRegion *component_bar = &cregs->component_registers; | ||
int rc; | ||
|
||
pci_bridge_initfn(d, TYPE_PCIE_BUS); | ||
pcie_port_init_reg(d); | ||
|
||
rc = msi_init(d, CXL_UPSTREAM_PORT_MSI_OFFSET, | ||
CXL_UPSTREAM_PORT_MSI_NR_VECTOR, true, true, errp); | ||
if (rc) { | ||
assert(rc == -ENOTSUP); | ||
goto err_bridge; | ||
} | ||
|
||
rc = pcie_cap_init(d, CXL_UPSTREAM_PORT_PCIE_CAP_OFFSET, | ||
PCI_EXP_TYPE_UPSTREAM, p->port, errp); | ||
if (rc < 0) { | ||
goto err_msi; | ||
} | ||
|
||
pcie_cap_flr_init(d); | ||
pcie_cap_deverr_init(d); | ||
rc = pcie_aer_init(d, PCI_ERR_VER, CXL_UPSTREAM_PORT_AER_OFFSET, | ||
PCI_ERR_SIZEOF, errp); | ||
if (rc) { | ||
goto err_cap; | ||
} | ||
|
||
cxl_cstate->dvsec_offset = CXL_UPSTREAM_PORT_DVSEC_OFFSET; | ||
cxl_cstate->pdev = d; | ||
build_dvsecs(cxl_cstate); | ||
cxl_component_register_block_init(OBJECT(d), cxl_cstate, TYPE_CXL_USP); | ||
pci_register_bar(d, CXL_COMPONENT_REG_BAR_IDX, | ||
PCI_BASE_ADDRESS_SPACE_MEMORY | | ||
PCI_BASE_ADDRESS_MEM_TYPE_64, | ||
component_bar); | ||
|
||
return; | ||
|
||
err_cap: | ||
pcie_cap_exit(d); | ||
err_msi: | ||
msi_uninit(d); | ||
err_bridge: | ||
pci_bridge_exitfn(d); | ||
} | ||
|
||
static void cxl_usp_exitfn(PCIDevice *d) | ||
{ | ||
pcie_aer_exit(d); | ||
pcie_cap_exit(d); | ||
msi_uninit(d); | ||
pci_bridge_exitfn(d); | ||
} | ||
|
||
static void cxl_upstream_class_init(ObjectClass *oc, void *data) | ||
{ | ||
DeviceClass *dc = DEVICE_CLASS(oc); | ||
PCIDeviceClass *k = PCI_DEVICE_CLASS(oc); | ||
|
||
k->is_bridge = true; | ||
k->config_write = cxl_usp_write_config; | ||
k->realize = cxl_usp_realize; | ||
k->exit = cxl_usp_exitfn; | ||
k->vendor_id = 0x19e5; /* Huawei */ | ||
k->device_id = 0xa128; /* Emulated CXL Switch Upstream Port */ | ||
k->revision = 0; | ||
set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); | ||
dc->desc = "CXL Switch Upstream Port"; | ||
dc->reset = cxl_usp_reset; | ||
} | ||
|
||
static const TypeInfo cxl_usp_info = { | ||
.name = TYPE_CXL_USP, | ||
.parent = TYPE_PCIE_PORT, | ||
.instance_size = sizeof(CXLUpstreamPort), | ||
.class_init = cxl_upstream_class_init, | ||
.interfaces = (InterfaceInfo[]) { | ||
{ INTERFACE_PCIE_DEVICE }, | ||
{ INTERFACE_CXL_DEVICE }, | ||
{ } | ||
}, | ||
}; | ||
|
||
static void cxl_usp_register_type(void) | ||
{ | ||
type_register_static(&cxl_usp_info); | ||
} | ||
|
||
type_init(cxl_usp_register_type); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters