| 1 | /* |
| 2 | * Emulated CXL Switch Upstream Port |
| 3 | * |
| 4 | * Copyright (c) 2022 Huawei Technologies. |
| 5 | * |
| 6 | * Based on xio3130_upstream.c |
| 7 | * |
| 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 9 | */ |
| 10 | |
| 11 | #include "qemu/osdep.h" |
| 12 | #include "qemu/log.h" |
| 13 | #include "hw/core/qdev-properties.h" |
| 14 | #include "hw/core/qdev-properties-system.h" |
| 15 | #include "hw/pci/msi.h" |
| 16 | #include "hw/pci/pcie.h" |
| 17 | #include "hw/pci/pcie_port.h" |
| 18 | #include "hw/pci-bridge/cxl_upstream_port.h" |
| 19 | /* |
| 20 | * Null value of all Fs suggested by IEEE RA guidelines for use of |
| 21 | * EU, OUI and CID |
| 22 | */ |
| 23 | #define UI64_NULL (~0ULL) |
| 24 | |
| 25 | #define CXL_UPSTREAM_PORT_MSI_NR_VECTOR 2 |
| 26 | |
| 27 | #define CXL_UPSTREAM_PORT_MSI_OFFSET 0x70 |
| 28 | #define CXL_UPSTREAM_PORT_PCIE_CAP_OFFSET 0x90 |
| 29 | #define CXL_UPSTREAM_PORT_AER_OFFSET 0x100 |
| 30 | #define CXL_UPSTREAM_PORT_SN_OFFSET \ |
| 31 | (CXL_UPSTREAM_PORT_AER_OFFSET + PCI_ERR_SIZEOF) |
| 32 | #define CXL_UPSTREAM_PORT_DVSEC_OFFSET \ |
| 33 | (CXL_UPSTREAM_PORT_SN_OFFSET + PCI_EXT_CAP_DSN_SIZEOF) |
| 34 | |
| 35 | CXLComponentState *cxl_usp_to_cstate(CXLUpstreamPort *usp) |
| 36 | { |
| 37 | return &usp->cxl_cstate; |
| 38 | } |
| 39 | |
| 40 | static void cxl_usp_dvsec_write_config(PCIDevice *dev, uint32_t addr, |
| 41 | uint32_t val, int len) |
| 42 | { |
| 43 | CXLUpstreamPort *usp = CXL_USP(dev); |
| 44 | |
| 45 | if (range_contains(&usp->cxl_cstate.dvsecs[EXTENSIONS_PORT_DVSEC], addr)) { |
| 46 | uint8_t *reg = &dev->config[addr]; |
| 47 | addr -= usp->cxl_cstate.dvsecs[EXTENSIONS_PORT_DVSEC].lob; |
| 48 | if (addr == PORT_CONTROL_OFFSET) { |
| 49 | if (pci_get_word(reg) & PORT_CONTROL_UNMASK_SBR) { |
| 50 | /* unmask SBR */ |
| 51 | qemu_log_mask(LOG_UNIMP, "SBR mask control is not supported\n"); |
| 52 | } |
| 53 | if (pci_get_word(reg) & PORT_CONTROL_ALT_MEMID_EN) { |
| 54 | /* Alt Memory & ID Space Enable */ |
| 55 | qemu_log_mask(LOG_UNIMP, |
| 56 | "Alt Memory & ID space is not supported\n"); |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | static void cxl_usp_write_config(PCIDevice *d, uint32_t address, |
| 63 | uint32_t val, int len) |
| 64 | { |
| 65 | CXLUpstreamPort *usp = CXL_USP(d); |
| 66 | |
| 67 | pcie_doe_write_config(&usp->doe_cdat, address, val, len); |
| 68 | pci_bridge_write_config(d, address, val, len); |
| 69 | pcie_cap_flr_write_config(d, address, val, len); |
| 70 | pcie_aer_write_config(d, address, val, len); |
| 71 | |
| 72 | cxl_usp_dvsec_write_config(d, address, val, len); |
| 73 | } |
| 74 | |
| 75 | static uint32_t cxl_usp_read_config(PCIDevice *d, uint32_t address, int len) |
| 76 | { |
| 77 | CXLUpstreamPort *usp = CXL_USP(d); |
| 78 | uint32_t val; |
| 79 | |
| 80 | if (pcie_doe_read_config(&usp->doe_cdat, address, len, &val)) { |
| 81 | return val; |
| 82 | } |
| 83 | |
| 84 | return pci_default_read_config(d, address, len); |
| 85 | } |
| 86 | |
| 87 | static void latch_registers(CXLUpstreamPort *usp) |
| 88 | { |
| 89 | uint32_t *reg_state = usp->cxl_cstate.crb.cache_mem_registers; |
| 90 | uint32_t *write_msk = usp->cxl_cstate.crb.cache_mem_regs_write_mask; |
| 91 | |
| 92 | cxl_component_register_init_common(reg_state, write_msk, |
| 93 | CXL2_UPSTREAM_PORT, usp->flitmode); |
| 94 | ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, TARGET_COUNT, 8); |
| 95 | } |
| 96 | |
| 97 | static void cxl_usp_reset(DeviceState *qdev) |
| 98 | { |
| 99 | PCIDevice *d = PCI_DEVICE(qdev); |
| 100 | CXLUpstreamPort *usp = CXL_USP(qdev); |
| 101 | |
| 102 | pci_bridge_reset(qdev); |
| 103 | pcie_cap_deverr_reset(d); |
| 104 | pcie_cap_fill_link_ep_usp(d, usp->width, usp->speed, usp->flitmode); |
| 105 | latch_registers(usp); |
| 106 | cxl_init_physical_port_control(&usp->perst); |
| 107 | } |
| 108 | |
| 109 | static void build_dvsecs(CXLUpstreamPort *usp) |
| 110 | { |
| 111 | CXLComponentState *cxl_cstate = &usp->cxl_cstate; |
| 112 | uint8_t *dvsec; |
| 113 | |
| 114 | dvsec = (uint8_t *)&(CXLDVSECPortExt){ |
| 115 | .status = 0x1, /* Port Power Management Init Complete */ |
| 116 | }; |
| 117 | cxl_component_create_dvsec(cxl_cstate, CXL2_UPSTREAM_PORT, |
| 118 | EXTENSIONS_PORT_DVSEC_LENGTH, |
| 119 | EXTENSIONS_PORT_DVSEC, |
| 120 | EXTENSIONS_PORT_DVSEC_REVID, dvsec); |
| 121 | dvsec = (uint8_t *)&(CXLDVSECPortFlexBus){ |
| 122 | .cap = 0x27, /* Cache, IO, Mem, non-MLD */ |
| 123 | .ctrl = 0x27, /* Cache, IO, Mem */ |
| 124 | .status = usp->flitmode ? 0x6 : 0x26, /* lack of 68B */ |
| 125 | .rcvd_mod_ts_data_phase1 = 0xef, /* WTF? */ |
| 126 | }; |
| 127 | cxl_component_create_dvsec(cxl_cstate, CXL2_UPSTREAM_PORT, |
| 128 | PCIE_CXL3_FLEXBUS_PORT_DVSEC_LENGTH, |
| 129 | PCIE_FLEXBUS_PORT_DVSEC, |
| 130 | PCIE_CXL3_FLEXBUS_PORT_DVSEC_REVID, dvsec); |
| 131 | |
| 132 | dvsec = (uint8_t *)&(CXLDVSECRegisterLocator){ |
| 133 | .rsvd = 0, |
| 134 | .reg0_base_lo = RBI_COMPONENT_REG | CXL_COMPONENT_REG_BAR_IDX, |
| 135 | .reg0_base_hi = 0, |
| 136 | }; |
| 137 | cxl_component_create_dvsec(cxl_cstate, CXL2_UPSTREAM_PORT, |
| 138 | REG_LOC_DVSEC_LENGTH, REG_LOC_DVSEC, |
| 139 | REG_LOC_DVSEC_REVID, dvsec); |
| 140 | } |
| 141 | |
| 142 | static bool cxl_doe_cdat_rsp(DOECap *doe_cap) |
| 143 | { |
| 144 | CDATObject *cdat = &CXL_USP(doe_cap->pdev)->cxl_cstate.cdat; |
| 145 | uint16_t ent; |
| 146 | void *base; |
| 147 | uint32_t len; |
| 148 | CDATReq *req = pcie_doe_get_write_mbox_ptr(doe_cap); |
| 149 | CDATRsp rsp; |
| 150 | |
| 151 | cxl_doe_cdat_update(&CXL_USP(doe_cap->pdev)->cxl_cstate, &error_fatal); |
| 152 | assert(cdat->entry_len); |
| 153 | |
| 154 | /* Discard if request length mismatched */ |
| 155 | if (pcie_doe_get_obj_len(req) < |
| 156 | DIV_ROUND_UP(sizeof(CDATReq), sizeof(uint32_t))) { |
| 157 | return false; |
| 158 | } |
| 159 | |
| 160 | ent = req->entry_handle; |
| 161 | if (ent >= cdat->entry_len) { |
| 162 | return false; |
| 163 | } |
| 164 | base = cdat->entry[ent].base; |
| 165 | len = cdat->entry[ent].length; |
| 166 | |
| 167 | rsp = (CDATRsp) { |
| 168 | .header = { |
| 169 | .vendor_id = CXL_VENDOR_ID, |
| 170 | .data_obj_type = CXL_DOE_TABLE_ACCESS, |
| 171 | .reserved = 0x0, |
| 172 | .length = DIV_ROUND_UP((sizeof(rsp) + len), sizeof(uint32_t)), |
| 173 | }, |
| 174 | .rsp_code = CXL_DOE_TAB_RSP, |
| 175 | .table_type = CXL_DOE_TAB_TYPE_CDAT, |
| 176 | .entry_handle = (ent < cdat->entry_len - 1) ? |
| 177 | ent + 1 : CXL_DOE_TAB_ENT_MAX, |
| 178 | }; |
| 179 | |
| 180 | memcpy(doe_cap->read_mbox, &rsp, sizeof(rsp)); |
| 181 | memcpy(doe_cap->read_mbox + DIV_ROUND_UP(sizeof(rsp), sizeof(uint32_t)), |
| 182 | base, len); |
| 183 | |
| 184 | doe_cap->read_mbox_len += rsp.header.length; |
| 185 | |
| 186 | return true; |
| 187 | } |
| 188 | |
| 189 | static DOEProtocol doe_cdat_prot[] = { |
| 190 | { CXL_VENDOR_ID, CXL_DOE_TABLE_ACCESS, cxl_doe_cdat_rsp }, |
| 191 | { } |
| 192 | }; |
| 193 | |
| 194 | enum { |
| 195 | CXL_USP_CDAT_SSLBIS_LAT, |
| 196 | CXL_USP_CDAT_SSLBIS_BW, |
| 197 | CXL_USP_CDAT_NUM_ENTRIES |
| 198 | }; |
| 199 | |
| 200 | static int build_cdat_table(CDATSubHeader ***cdat_table, void *priv) |
| 201 | { |
| 202 | CDATSslbis *sslbis_latency; |
| 203 | CDATSslbis *sslbis_bandwidth; |
| 204 | CXLUpstreamPort *us = CXL_USP(priv); |
| 205 | PCIBus *bus = &PCI_BRIDGE(us)->sec_bus; |
| 206 | int devfn, sslbis_size, i; |
| 207 | int count = 0; |
| 208 | uint16_t port_ids[256]; |
| 209 | |
| 210 | for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { |
| 211 | PCIDevice *d = bus->devices[devfn]; |
| 212 | PCIEPort *port; |
| 213 | |
| 214 | if (!d || !pci_is_express(d) || !d->exp.exp_cap) { |
| 215 | continue; |
| 216 | } |
| 217 | |
| 218 | /* |
| 219 | * Whilst the PCI express spec doesn't allow anything other than |
| 220 | * downstream ports on this bus, let us be a little paranoid |
| 221 | */ |
| 222 | if (!object_dynamic_cast(OBJECT(d), TYPE_PCIE_PORT)) { |
| 223 | continue; |
| 224 | } |
| 225 | |
| 226 | port = PCIE_PORT(d); |
| 227 | port_ids[count] = port->port; |
| 228 | count++; |
| 229 | } |
| 230 | |
| 231 | /* May not yet have any ports - try again later */ |
| 232 | if (count == 0) { |
| 233 | return 0; |
| 234 | } |
| 235 | |
| 236 | sslbis_size = sizeof(CDATSslbis) + sizeof(*sslbis_latency->sslbe) * count; |
| 237 | sslbis_latency = g_malloc(sslbis_size); |
| 238 | *sslbis_latency = (CDATSslbis) { |
| 239 | .sslbis_header = { |
| 240 | .header = { |
| 241 | .type = CDAT_TYPE_SSLBIS, |
| 242 | .length = sslbis_size, |
| 243 | }, |
| 244 | .data_type = HMAT_LB_DATA_TYPE_ACCESS_LATENCY, |
| 245 | .entry_base_unit = 10000, |
| 246 | }, |
| 247 | }; |
| 248 | |
| 249 | for (i = 0; i < count; i++) { |
| 250 | sslbis_latency->sslbe[i] = (CDATSslbe) { |
| 251 | .port_x_id = CDAT_PORT_ID_USP, |
| 252 | .port_y_id = port_ids[i], |
| 253 | .latency_bandwidth = 15, /* 150ns */ |
| 254 | }; |
| 255 | } |
| 256 | |
| 257 | sslbis_bandwidth = g_malloc(sslbis_size); |
| 258 | *sslbis_bandwidth = (CDATSslbis) { |
| 259 | .sslbis_header = { |
| 260 | .header = { |
| 261 | .type = CDAT_TYPE_SSLBIS, |
| 262 | .length = sslbis_size, |
| 263 | }, |
| 264 | .data_type = HMAT_LB_DATA_TYPE_ACCESS_BANDWIDTH, |
| 265 | .entry_base_unit = 1024, |
| 266 | }, |
| 267 | }; |
| 268 | |
| 269 | for (i = 0; i < count; i++) { |
| 270 | sslbis_bandwidth->sslbe[i] = (CDATSslbe) { |
| 271 | .port_x_id = CDAT_PORT_ID_USP, |
| 272 | .port_y_id = port_ids[i], |
| 273 | .latency_bandwidth = 16, /* 16 GB/s */ |
| 274 | }; |
| 275 | } |
| 276 | |
| 277 | *cdat_table = g_new0(CDATSubHeader *, CXL_USP_CDAT_NUM_ENTRIES); |
| 278 | |
| 279 | /* Header always at start of structure */ |
| 280 | (*cdat_table)[CXL_USP_CDAT_SSLBIS_LAT] = (CDATSubHeader *)sslbis_latency; |
| 281 | (*cdat_table)[CXL_USP_CDAT_SSLBIS_BW] = (CDATSubHeader *)sslbis_bandwidth; |
| 282 | |
| 283 | return CXL_USP_CDAT_NUM_ENTRIES; |
| 284 | } |
| 285 | |
| 286 | static void free_default_cdat_table(CDATSubHeader **cdat_table, int num, |
| 287 | void *priv) |
| 288 | { |
| 289 | int i; |
| 290 | |
| 291 | for (i = 0; i < num; i++) { |
| 292 | g_free(cdat_table[i]); |
| 293 | } |
| 294 | g_free(cdat_table); |
| 295 | } |
| 296 | |
| 297 | static void cxl_usp_realize(PCIDevice *d, Error **errp) |
| 298 | { |
| 299 | ERRP_GUARD(); |
| 300 | PCIEPort *p = PCIE_PORT(d); |
| 301 | CXLUpstreamPort *usp = CXL_USP(d); |
| 302 | CXLComponentState *cxl_cstate = &usp->cxl_cstate; |
| 303 | ComponentRegisters *cregs = &cxl_cstate->crb; |
| 304 | MemoryRegion *component_bar = &cregs->component_registers; |
| 305 | int rc; |
| 306 | |
| 307 | pci_bridge_initfn(d, TYPE_PCIE_BUS); |
| 308 | pcie_port_init_reg(d); |
| 309 | |
| 310 | rc = msi_init(d, CXL_UPSTREAM_PORT_MSI_OFFSET, |
| 311 | CXL_UPSTREAM_PORT_MSI_NR_VECTOR, true, true, errp); |
| 312 | if (rc) { |
| 313 | assert(rc == -ENOTSUP); |
| 314 | goto err_bridge; |
| 315 | } |
| 316 | |
| 317 | rc = pcie_cap_init(d, CXL_UPSTREAM_PORT_PCIE_CAP_OFFSET, |
| 318 | PCI_EXP_TYPE_UPSTREAM, p->port, errp); |
| 319 | if (rc < 0) { |
| 320 | goto err_msi; |
| 321 | } |
| 322 | |
| 323 | pcie_cap_flr_init(d); |
| 324 | pcie_cap_deverr_init(d); |
| 325 | rc = pcie_aer_init(d, PCI_ERR_VER, CXL_UPSTREAM_PORT_AER_OFFSET, |
| 326 | PCI_ERR_SIZEOF, errp); |
| 327 | if (rc) { |
| 328 | goto err_cap; |
| 329 | } |
| 330 | if (usp->sn != UI64_NULL) { |
| 331 | pcie_dev_ser_num_init(d, CXL_UPSTREAM_PORT_SN_OFFSET, usp->sn); |
| 332 | } |
| 333 | cxl_cstate->dvsec_offset = CXL_UPSTREAM_PORT_DVSEC_OFFSET; |
| 334 | cxl_cstate->pdev = d; |
| 335 | build_dvsecs(usp); |
| 336 | cxl_component_register_block_init(OBJECT(d), cxl_cstate, TYPE_CXL_USP); |
| 337 | pci_register_bar(d, CXL_COMPONENT_REG_BAR_IDX, |
| 338 | PCI_BASE_ADDRESS_SPACE_MEMORY | |
| 339 | PCI_BASE_ADDRESS_MEM_TYPE_64, |
| 340 | component_bar); |
| 341 | |
| 342 | pcie_doe_init(d, &usp->doe_cdat, cxl_cstate->dvsec_offset, doe_cdat_prot, |
| 343 | true, 1); |
| 344 | |
| 345 | cxl_cstate->cdat.build_cdat_table = build_cdat_table; |
| 346 | cxl_cstate->cdat.free_cdat_table = free_default_cdat_table; |
| 347 | cxl_cstate->cdat.private = d; |
| 348 | if (!cxl_doe_cdat_init(cxl_cstate, errp)) { |
| 349 | goto err_cap; |
| 350 | } |
| 351 | |
| 352 | return; |
| 353 | |
| 354 | err_cap: |
| 355 | pcie_cap_exit(d); |
| 356 | err_msi: |
| 357 | msi_uninit(d); |
| 358 | err_bridge: |
| 359 | pci_bridge_exitfn(d); |
| 360 | } |
| 361 | |
| 362 | static void cxl_usp_exitfn(PCIDevice *d) |
| 363 | { |
| 364 | pcie_aer_exit(d); |
| 365 | pcie_cap_exit(d); |
| 366 | msi_uninit(d); |
| 367 | pci_bridge_exitfn(d); |
| 368 | } |
| 369 | |
| 370 | static const Property cxl_upstream_props[] = { |
| 371 | DEFINE_PROP_UINT64("sn", CXLUpstreamPort, sn, UI64_NULL), |
| 372 | DEFINE_PROP_STRING("cdat", CXLUpstreamPort, cxl_cstate.cdat.filename), |
| 373 | DEFINE_PROP_PCIE_LINK_SPEED("x-speed", CXLUpstreamPort, |
| 374 | speed, PCIE_LINK_SPEED_32), |
| 375 | DEFINE_PROP_PCIE_LINK_WIDTH("x-width", CXLUpstreamPort, |
| 376 | width, PCIE_LINK_WIDTH_16), |
| 377 | DEFINE_PROP_BOOL("x-256b-flit", CXLUpstreamPort, flitmode, false), |
| 378 | }; |
| 379 | |
| 380 | static void cxl_upstream_class_init(ObjectClass *oc, const void *data) |
| 381 | { |
| 382 | DeviceClass *dc = DEVICE_CLASS(oc); |
| 383 | PCIDeviceClass *k = PCI_DEVICE_CLASS(oc); |
| 384 | |
| 385 | k->config_write = cxl_usp_write_config; |
| 386 | k->config_read = cxl_usp_read_config; |
| 387 | k->realize = cxl_usp_realize; |
| 388 | k->exit = cxl_usp_exitfn; |
| 389 | k->vendor_id = 0x19e5; /* Huawei */ |
| 390 | k->device_id = 0xa128; /* Emulated CXL Switch Upstream Port */ |
| 391 | k->revision = 0; |
| 392 | set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); |
| 393 | dc->desc = "CXL Switch Upstream Port"; |
| 394 | device_class_set_legacy_reset(dc, cxl_usp_reset); |
| 395 | device_class_set_props(dc, cxl_upstream_props); |
| 396 | } |
| 397 | |
| 398 | static const TypeInfo cxl_usp_info = { |
| 399 | .name = TYPE_CXL_USP, |
| 400 | .parent = TYPE_PCIE_PORT, |
| 401 | .instance_size = sizeof(CXLUpstreamPort), |
| 402 | .class_init = cxl_upstream_class_init, |
| 403 | .interfaces = (const InterfaceInfo[]) { |
| 404 | { INTERFACE_PCIE_DEVICE }, |
| 405 | { INTERFACE_CXL_DEVICE }, |
| 406 | { } |
| 407 | }, |
| 408 | }; |
| 409 | |
| 410 | static void cxl_usp_register_type(void) |
| 411 | { |
| 412 | type_register_static(&cxl_usp_info); |
| 413 | } |
| 414 | |
| 415 | type_init(cxl_usp_register_type); |