Skip to content

Commit 34e37b4

Browse files
committed
cxl/port: Enable HDM Capability after validating DVSEC Ranges
CXL memory expanders that support the CXL 2.0 memory device class code include an "HDM Decoder Capability" mechanism to supplant the "CXL DVSEC Range" mechanism originally defined in CXL 1.1. Both mechanisms depend on a "mem_enable" bit being set in configuration space before either mechanism activates. When the HDM Decoder Capability is enabled the CXL DVSEC Range settings are ignored. Previously, the cxl_mem driver was relying on platform-firmware to set "mem_enable". That is an invalid assumption as there is no requirement that platform-firmware sets the bit before the driver sees a device, especially in hot-plug scenarios. Additionally, ACPI-platforms that support CXL 2.0 devices also support the ACPI CEDT (CXL Early Discovery Table). That table outlines the platform permissible address ranges for CXL operation. So, there is a need for the driver to set "mem_enable", and there is information available to determine the validity of the CXL DVSEC Ranges. Arrange for the driver to optionally enable the HDM Decoder Capability if "mem_enable" was not set by platform firmware, or the CXL DVSEC Range configuration was invalid. Be careful to only disable memory decode if the kernel was the one to enable it. In other words, if CXL is backing all of kernel memory at boot the device needs to maintain "mem_enable" and "HDM Decoder enable" all the way up to handoff back to platform firmware (e.g. ACPI S5 state entry may require CXL memory to stay active). Fixes: 560f785 ("cxl/pci: Retrieve CXL DVSEC memory info") Cc: Dan Carpenter <dan.carpenter@oracle.com> [dan: fix early terminiation of range-allowed loop] Cc: Ariel Sibley <ariel.sibley@microchip.com> [ariel: Memory_size must be non-zero] Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com> Reviewed-by: Ira Weiny <ira.weiny@intel.com> Link: https://lore.kernel.org/r/165307136375.2499769.861793697156744166.stgit@dwillia2-xfh Signed-off-by: Dan Williams <dan.j.williams@intel.com>
1 parent fcfbc93 commit 34e37b4

1 file changed

Lines changed: 152 additions & 15 deletions

File tree

drivers/cxl/core/pci.c

Lines changed: 152 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -175,38 +175,169 @@ static int wait_for_valid(struct cxl_dev_state *cxlds)
175175
return -ETIMEDOUT;
176176
}
177177

178+
static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
179+
{
180+
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
181+
int d = cxlds->cxl_dvsec;
182+
u16 ctrl;
183+
int rc;
184+
185+
rc = pci_read_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, &ctrl);
186+
if (rc < 0)
187+
return rc;
188+
189+
if ((ctrl & CXL_DVSEC_MEM_ENABLE) == val)
190+
return 1;
191+
ctrl &= ~CXL_DVSEC_MEM_ENABLE;
192+
ctrl |= val;
193+
194+
rc = pci_write_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, ctrl);
195+
if (rc < 0)
196+
return rc;
197+
198+
return 0;
199+
}
200+
201+
static void clear_mem_enable(void *cxlds)
202+
{
203+
cxl_set_mem_enable(cxlds, 0);
204+
}
205+
206+
static int devm_cxl_enable_mem(struct device *host, struct cxl_dev_state *cxlds)
207+
{
208+
int rc;
209+
210+
rc = cxl_set_mem_enable(cxlds, CXL_DVSEC_MEM_ENABLE);
211+
if (rc < 0)
212+
return rc;
213+
if (rc > 0)
214+
return 0;
215+
return devm_add_action_or_reset(host, clear_mem_enable, cxlds);
216+
}
217+
218+
static bool range_contains(struct range *r1, struct range *r2)
219+
{
220+
return r1->start <= r2->start && r1->end >= r2->end;
221+
}
222+
223+
/* require dvsec ranges to be covered by a locked platform window */
224+
static int dvsec_range_allowed(struct device *dev, void *arg)
225+
{
226+
struct range *dev_range = arg;
227+
struct cxl_decoder *cxld;
228+
struct range root_range;
229+
230+
if (!is_root_decoder(dev))
231+
return 0;
232+
233+
cxld = to_cxl_decoder(dev);
234+
235+
if (!(cxld->flags & CXL_DECODER_F_LOCK))
236+
return 0;
237+
if (!(cxld->flags & CXL_DECODER_F_RAM))
238+
return 0;
239+
240+
root_range = (struct range) {
241+
.start = cxld->platform_res.start,
242+
.end = cxld->platform_res.end,
243+
};
244+
245+
return range_contains(&root_range, dev_range);
246+
}
247+
248+
static void disable_hdm(void *_cxlhdm)
249+
{
250+
u32 global_ctrl;
251+
struct cxl_hdm *cxlhdm = _cxlhdm;
252+
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
253+
254+
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
255+
writel(global_ctrl & ~CXL_HDM_DECODER_ENABLE,
256+
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
257+
}
258+
259+
static int devm_cxl_enable_hdm(struct device *host, struct cxl_hdm *cxlhdm)
260+
{
261+
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
262+
u32 global_ctrl;
263+
264+
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
265+
writel(global_ctrl | CXL_HDM_DECODER_ENABLE,
266+
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
267+
268+
return devm_add_action_or_reset(host, disable_hdm, cxlhdm);
269+
}
270+
178271
static bool __cxl_hdm_decode_init(struct cxl_dev_state *cxlds,
179272
struct cxl_hdm *cxlhdm,
180273
struct cxl_endpoint_dvsec_info *info)
181274
{
182275
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
183-
bool global_enable;
276+
struct cxl_port *port = cxlhdm->port;
277+
struct device *dev = cxlds->dev;
278+
struct cxl_port *root;
279+
int i, rc, allowed;
184280
u32 global_ctrl;
185281

186282
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
187-
global_enable = global_ctrl & CXL_HDM_DECODER_ENABLE;
283+
284+
/*
285+
* If the HDM Decoder Capability is already enabled then assume
286+
* that some other agent like platform firmware set it up.
287+
*/
288+
if (global_ctrl & CXL_HDM_DECODER_ENABLE) {
289+
rc = devm_cxl_enable_mem(&port->dev, cxlds);
290+
if (rc)
291+
return false;
292+
return true;
293+
}
294+
295+
root = to_cxl_port(port->dev.parent);
296+
while (!is_cxl_root(root) && is_cxl_port(root->dev.parent))
297+
root = to_cxl_port(root->dev.parent);
298+
if (!is_cxl_root(root)) {
299+
dev_err(dev, "Failed to acquire root port for HDM enable\n");
300+
return false;
301+
}
302+
303+
for (i = 0, allowed = 0; info->mem_enabled && i < info->ranges; i++) {
304+
struct device *cxld_dev;
305+
306+
cxld_dev = device_find_child(&root->dev, &info->dvsec_range[i],
307+
dvsec_range_allowed);
308+
if (!cxld_dev) {
309+
dev_dbg(dev, "DVSEC Range%d denied by platform\n", i);
310+
continue;
311+
}
312+
dev_dbg(dev, "DVSEC Range%d allowed by platform\n", i);
313+
put_device(cxld_dev);
314+
allowed++;
315+
}
316+
317+
if (!allowed) {
318+
cxl_set_mem_enable(cxlds, 0);
319+
info->mem_enabled = 0;
320+
}
188321

189322
/*
190323
* Per CXL 2.0 Section 8.1.3.8.3 and 8.1.3.8.4 DVSEC CXL Range 1 Base
191324
* [High,Low] when HDM operation is enabled the range register values
192325
* are ignored by the device, but the spec also recommends matching the
193326
* DVSEC Range 1,2 to HDM Decoder Range 0,1. So, non-zero info->ranges
194327
* are expected even though Linux does not require or maintain that
195-
* match.
328+
* match. If at least one DVSEC range is enabled and allowed, skip HDM
329+
* Decoder Capability Enable.
196330
*/
197-
if (!global_enable && info->mem_enabled && info->ranges)
331+
if (info->mem_enabled)
198332
return false;
199333

200-
/*
201-
* Permanently (for this boot at least) opt the device into HDM
202-
* operation. Individual HDM decoders still need to be enabled after
203-
* this point.
204-
*/
205-
if (!global_enable) {
206-
dev_dbg(cxlds->dev, "Enabling HDM decode\n");
207-
writel(global_ctrl | CXL_HDM_DECODER_ENABLE,
208-
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
209-
}
334+
rc = devm_cxl_enable_hdm(&port->dev, cxlhdm);
335+
if (rc)
336+
return false;
337+
338+
rc = devm_cxl_enable_mem(&port->dev, cxlds);
339+
if (rc)
340+
return false;
210341

211342
return true;
212343
}
@@ -261,9 +392,14 @@ int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm)
261392
return rc;
262393
}
263394

395+
/*
396+
* The current DVSEC values are moot if the memory capability is
397+
* disabled, and they will remain moot after the HDM Decoder
398+
* capability is enabled.
399+
*/
264400
info.mem_enabled = FIELD_GET(CXL_DVSEC_MEM_ENABLE, ctrl);
265401
if (!info.mem_enabled)
266-
return 0;
402+
goto hdm_init;
267403

268404
for (i = 0; i < hdm_count; i++) {
269405
u64 base, size;
@@ -312,6 +448,7 @@ int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm)
312448
* If DVSEC ranges are being used instead of HDM decoder registers there
313449
* is no use in trying to manage those.
314450
*/
451+
hdm_init:
315452
if (!__cxl_hdm_decode_init(cxlds, cxlhdm, &info)) {
316453
dev_err(dev,
317454
"Legacy range registers configuration prevents HDM operation.\n");

0 commit comments

Comments
 (0)