Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cxl/port: Enable the HDM decoder capability for switch ports

Derick noticed, when testing hot plug, that hot-add behaves nominally
after a removal. However, if the hot-add is done without a prior
removal, CXL.mem accesses fail. It turns out that the original
implementation of the port driver and region programming wrongly assumed
that platform-firmware always enables the host-bridge HDM decoder
capability. Add support turning on switch-level HDM decoders in the case
where platform-firmware has not.

The implementation is careful to only arrange for the enable to be
undone if the current instance of the driver was the one that did the
enable. This is to interoperate with platform-firmware that may expect
CXL.mem to remain active after the driver is shutdown. This comes at the
cost of potentially not shutting down the enable on kexec flows, but it
is mitigated by the fact that the related HDM decoders still need to be
enabled on an individual basis.

Cc: <stable@vger.kernel.org>
Reported-by: Derick Marks <derick.w.marks@intel.com>
Fixes: 54cdbf845cf7 ("cxl/port: Add a driver for 'struct cxl_port' objects")
Reviewed-by: Ira Weiny <ira.weiny@intel.com>
Link: https://lore.kernel.org/r/168437998331.403037.15719879757678389217.stgit@dwillia2-xfh.jf.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

+49 -9
+23 -4
drivers/cxl/core/pci.c
··· 241 241 hdm + CXL_HDM_DECODER_CTRL_OFFSET); 242 242 } 243 243 244 - static int devm_cxl_enable_hdm(struct device *host, struct cxl_hdm *cxlhdm) 244 + int devm_cxl_enable_hdm(struct cxl_port *port, struct cxl_hdm *cxlhdm) 245 245 { 246 - void __iomem *hdm = cxlhdm->regs.hdm_decoder; 246 + void __iomem *hdm; 247 247 u32 global_ctrl; 248 248 249 + /* 250 + * If the hdm capability was not mapped there is nothing to enable and 251 + * the caller is responsible for what happens next. For example, 252 + * emulate a passthrough decoder. 253 + */ 254 + if (IS_ERR(cxlhdm)) 255 + return 0; 256 + 257 + hdm = cxlhdm->regs.hdm_decoder; 249 258 global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET); 259 + 260 + /* 261 + * If the HDM decoder capability was enabled on entry, skip 262 + * registering disable_hdm() since this decode capability may be 263 + * owned by platform firmware. 264 + */ 265 + if (global_ctrl & CXL_HDM_DECODER_ENABLE) 266 + return 0; 267 + 250 268 writel(global_ctrl | CXL_HDM_DECODER_ENABLE, 251 269 hdm + CXL_HDM_DECODER_CTRL_OFFSET); 252 270 253 - return devm_add_action_or_reset(host, disable_hdm, cxlhdm); 271 + return devm_add_action_or_reset(&port->dev, disable_hdm, cxlhdm); 254 272 } 273 + EXPORT_SYMBOL_NS_GPL(devm_cxl_enable_hdm, CXL); 255 274 256 275 int cxl_dvsec_rr_decode(struct device *dev, int d, 257 276 struct cxl_endpoint_dvsec_info *info) ··· 444 425 if (info->mem_enabled) 445 426 return 0; 446 427 447 - rc = devm_cxl_enable_hdm(&port->dev, cxlhdm); 428 + rc = devm_cxl_enable_hdm(port, cxlhdm); 448 429 if (rc) 449 430 return rc; 450 431
+1
drivers/cxl/cxl.h
··· 710 710 struct cxl_hdm; 711 711 struct cxl_hdm *devm_cxl_setup_hdm(struct cxl_port *port, 712 712 struct cxl_endpoint_dvsec_info *info); 713 + int devm_cxl_enable_hdm(struct cxl_port *port, struct cxl_hdm *cxlhdm); 713 714 int devm_cxl_enumerate_decoders(struct cxl_hdm *cxlhdm, 714 715 struct cxl_endpoint_dvsec_info *info); 715 716 int devm_cxl_add_passthrough_decoder(struct cxl_port *port);
+9 -5
drivers/cxl/port.c
··· 60 60 static int cxl_switch_port_probe(struct cxl_port *port) 61 61 { 62 62 struct cxl_hdm *cxlhdm; 63 - int rc; 63 + int rc, nr_dports; 64 64 65 - rc = devm_cxl_port_enumerate_dports(port); 66 - if (rc < 0) 67 - return rc; 65 + nr_dports = devm_cxl_port_enumerate_dports(port); 66 + if (nr_dports < 0) 67 + return nr_dports; 68 68 69 69 cxlhdm = devm_cxl_setup_hdm(port, NULL); 70 + rc = devm_cxl_enable_hdm(port, cxlhdm); 71 + if (rc) 72 + return rc; 73 + 70 74 if (!IS_ERR(cxlhdm)) 71 75 return devm_cxl_enumerate_decoders(cxlhdm, NULL); 72 76 ··· 79 75 return PTR_ERR(cxlhdm); 80 76 } 81 77 82 - if (rc == 1) { 78 + if (nr_dports == 1) { 83 79 dev_dbg(&port->dev, "Fallback to passthrough decoder\n"); 84 80 return devm_cxl_add_passthrough_decoder(port); 85 81 }
+1
tools/testing/cxl/Kbuild
··· 6 6 ldflags-y += --wrap=nvdimm_bus_register 7 7 ldflags-y += --wrap=devm_cxl_port_enumerate_dports 8 8 ldflags-y += --wrap=devm_cxl_setup_hdm 9 + ldflags-y += --wrap=devm_cxl_enable_hdm 9 10 ldflags-y += --wrap=devm_cxl_add_passthrough_decoder 10 11 ldflags-y += --wrap=devm_cxl_enumerate_decoders 11 12 ldflags-y += --wrap=cxl_await_media_ready
+15
tools/testing/cxl/test/mock.c
··· 149 149 } 150 150 EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_setup_hdm, CXL); 151 151 152 + int __wrap_devm_cxl_enable_hdm(struct cxl_port *port, struct cxl_hdm *cxlhdm) 153 + { 154 + int index, rc; 155 + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); 156 + 157 + if (ops && ops->is_mock_port(port->uport)) 158 + rc = 0; 159 + else 160 + rc = devm_cxl_enable_hdm(port, cxlhdm); 161 + put_cxl_mock_ops(index); 162 + 163 + return rc; 164 + } 165 + EXPORT_SYMBOL_NS_GPL(__wrap_devm_cxl_enable_hdm, CXL); 166 + 152 167 int __wrap_devm_cxl_add_passthrough_decoder(struct cxl_port *port) 153 168 { 154 169 int rc, index;