cxl: Unify debug messages when calling devm_cxl_add_port()
[linux-2.6-block.git] / drivers / cxl / acpi.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright(c) 2021 Intel Corporation. All rights reserved. */
3 #include <linux/platform_device.h>
4 #include <linux/module.h>
5 #include <linux/device.h>
6 #include <linux/kernel.h>
7 #include <linux/acpi.h>
8 #include <linux/pci.h>
9 #include "cxlpci.h"
10 #include "cxl.h"
11
12 static unsigned long cfmws_to_decoder_flags(int restrictions)
13 {
14         unsigned long flags = CXL_DECODER_F_ENABLE;
15
16         if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE2)
17                 flags |= CXL_DECODER_F_TYPE2;
18         if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE3)
19                 flags |= CXL_DECODER_F_TYPE3;
20         if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE)
21                 flags |= CXL_DECODER_F_RAM;
22         if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_PMEM)
23                 flags |= CXL_DECODER_F_PMEM;
24         if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_FIXED)
25                 flags |= CXL_DECODER_F_LOCK;
26
27         return flags;
28 }
29
30 static int cxl_acpi_cfmws_verify(struct device *dev,
31                                  struct acpi_cedt_cfmws *cfmws)
32 {
33         int rc, expected_len;
34         unsigned int ways;
35
36         if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) {
37                 dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n");
38                 return -EINVAL;
39         }
40
41         if (!IS_ALIGNED(cfmws->base_hpa, SZ_256M)) {
42                 dev_err(dev, "CFMWS Base HPA not 256MB aligned\n");
43                 return -EINVAL;
44         }
45
46         if (!IS_ALIGNED(cfmws->window_size, SZ_256M)) {
47                 dev_err(dev, "CFMWS Window Size not 256MB aligned\n");
48                 return -EINVAL;
49         }
50
51         rc = cxl_to_ways(cfmws->interleave_ways, &ways);
52         if (rc) {
53                 dev_err(dev, "CFMWS Interleave Ways (%d) invalid\n",
54                         cfmws->interleave_ways);
55                 return -EINVAL;
56         }
57
58         expected_len = struct_size(cfmws, interleave_targets, ways);
59
60         if (cfmws->header.length < expected_len) {
61                 dev_err(dev, "CFMWS length %d less than expected %d\n",
62                         cfmws->header.length, expected_len);
63                 return -EINVAL;
64         }
65
66         if (cfmws->header.length > expected_len)
67                 dev_dbg(dev, "CFMWS length %d greater than expected %d\n",
68                         cfmws->header.length, expected_len);
69
70         return 0;
71 }
72
73 struct cxl_cfmws_context {
74         struct device *dev;
75         struct cxl_port *root_port;
76         struct resource *cxl_res;
77         int id;
78 };
79
80 static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
81                            const unsigned long end)
82 {
83         int target_map[CXL_DECODER_MAX_INTERLEAVE];
84         struct cxl_cfmws_context *ctx = arg;
85         struct cxl_port *root_port = ctx->root_port;
86         struct resource *cxl_res = ctx->cxl_res;
87         struct cxl_root_decoder *cxlrd;
88         struct device *dev = ctx->dev;
89         struct acpi_cedt_cfmws *cfmws;
90         struct cxl_decoder *cxld;
91         unsigned int ways, i, ig;
92         struct resource *res;
93         int rc;
94
95         cfmws = (struct acpi_cedt_cfmws *) header;
96
97         rc = cxl_acpi_cfmws_verify(dev, cfmws);
98         if (rc) {
99                 dev_err(dev, "CFMWS range %#llx-%#llx not registered\n",
100                         cfmws->base_hpa,
101                         cfmws->base_hpa + cfmws->window_size - 1);
102                 return 0;
103         }
104
105         rc = cxl_to_ways(cfmws->interleave_ways, &ways);
106         if (rc)
107                 return rc;
108         rc = cxl_to_granularity(cfmws->granularity, &ig);
109         if (rc)
110                 return rc;
111         for (i = 0; i < ways; i++)
112                 target_map[i] = cfmws->interleave_targets[i];
113
114         res = kzalloc(sizeof(*res), GFP_KERNEL);
115         if (!res)
116                 return -ENOMEM;
117
118         res->name = kasprintf(GFP_KERNEL, "CXL Window %d", ctx->id++);
119         if (!res->name)
120                 goto err_name;
121
122         res->start = cfmws->base_hpa;
123         res->end = cfmws->base_hpa + cfmws->window_size - 1;
124         res->flags = IORESOURCE_MEM;
125
126         /* add to the local resource tracking to establish a sort order */
127         rc = insert_resource(cxl_res, res);
128         if (rc)
129                 goto err_insert;
130
131         cxlrd = cxl_root_decoder_alloc(root_port, ways);
132         if (IS_ERR(cxlrd))
133                 return 0;
134
135         cxld = &cxlrd->cxlsd.cxld;
136         cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions);
137         cxld->target_type = CXL_DECODER_EXPANDER;
138         cxld->hpa_range = (struct range) {
139                 .start = res->start,
140                 .end = res->end,
141         };
142         cxld->interleave_ways = ways;
143         /*
144          * Minimize the x1 granularity to advertise support for any
145          * valid region granularity
146          */
147         if (ways == 1)
148                 ig = CXL_DECODER_MIN_GRANULARITY;
149         cxld->interleave_granularity = ig;
150
151         rc = cxl_decoder_add(cxld, target_map);
152         if (rc)
153                 put_device(&cxld->dev);
154         else
155                 rc = cxl_decoder_autoremove(dev, cxld);
156         if (rc) {
157                 dev_err(dev, "Failed to add decode range [%#llx - %#llx]\n",
158                         cxld->hpa_range.start, cxld->hpa_range.end);
159                 return 0;
160         }
161         dev_dbg(dev, "add: %s node: %d range [%#llx - %#llx]\n",
162                 dev_name(&cxld->dev),
163                 phys_to_target_node(cxld->hpa_range.start),
164                 cxld->hpa_range.start, cxld->hpa_range.end);
165
166         return 0;
167
168 err_insert:
169         kfree(res->name);
170 err_name:
171         kfree(res);
172         return -ENOMEM;
173 }
174
175 __mock struct acpi_device *to_cxl_host_bridge(struct device *host,
176                                               struct device *dev)
177 {
178         struct acpi_device *adev = to_acpi_device(dev);
179
180         if (!acpi_pci_find_root(adev->handle))
181                 return NULL;
182
183         if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0)
184                 return adev;
185         return NULL;
186 }
187
188 /*
189  * A host bridge is a dport to a CFMWS decode and it is a uport to the
190  * dport (PCIe Root Ports) in the host bridge.
191  */
192 static int add_host_bridge_uport(struct device *match, void *arg)
193 {
194         struct cxl_port *root_port = arg;
195         struct device *host = root_port->dev.parent;
196         struct acpi_device *bridge = to_cxl_host_bridge(host, match);
197         struct acpi_pci_root *pci_root;
198         struct cxl_dport *dport;
199         struct cxl_port *port;
200         int rc;
201
202         if (!bridge)
203                 return 0;
204
205         dport = cxl_find_dport_by_dev(root_port, match);
206         if (!dport) {
207                 dev_dbg(host, "host bridge expected and not found\n");
208                 return 0;
209         }
210
211         /*
212          * Note that this lookup already succeeded in
213          * to_cxl_host_bridge(), so no need to check for failure here
214          */
215         pci_root = acpi_pci_find_root(bridge->handle);
216         rc = devm_cxl_register_pci_bus(host, match, pci_root->bus);
217         if (rc)
218                 return rc;
219
220         port = devm_cxl_add_port(host, match, dport->component_reg_phys, dport);
221         if (IS_ERR(port))
222                 return PTR_ERR(port);
223
224         return 0;
225 }
226
227 struct cxl_chbs_context {
228         struct device *dev;
229         unsigned long long uid;
230         resource_size_t chbcr;
231 };
232
233 static int cxl_get_chbcr(union acpi_subtable_headers *header, void *arg,
234                          const unsigned long end)
235 {
236         struct cxl_chbs_context *ctx = arg;
237         struct acpi_cedt_chbs *chbs;
238
239         if (ctx->chbcr)
240                 return 0;
241
242         chbs = (struct acpi_cedt_chbs *) header;
243
244         if (ctx->uid != chbs->uid)
245                 return 0;
246         ctx->chbcr = chbs->base;
247
248         return 0;
249 }
250
251 static int add_host_bridge_dport(struct device *match, void *arg)
252 {
253         acpi_status status;
254         unsigned long long uid;
255         struct cxl_dport *dport;
256         struct cxl_chbs_context ctx;
257         struct cxl_port *root_port = arg;
258         struct device *host = root_port->dev.parent;
259         struct acpi_device *bridge = to_cxl_host_bridge(host, match);
260
261         if (!bridge)
262                 return 0;
263
264         status = acpi_evaluate_integer(bridge->handle, METHOD_NAME__UID, NULL,
265                                        &uid);
266         if (status != AE_OK) {
267                 dev_err(host, "unable to retrieve _UID of %s\n",
268                         dev_name(match));
269                 return -ENODEV;
270         }
271
272         ctx = (struct cxl_chbs_context) {
273                 .dev = host,
274                 .uid = uid,
275         };
276         acpi_table_parse_cedt(ACPI_CEDT_TYPE_CHBS, cxl_get_chbcr, &ctx);
277
278         if (ctx.chbcr == 0) {
279                 dev_warn(host, "No CHBS found for Host Bridge: %s\n",
280                          dev_name(match));
281                 return 0;
282         }
283
284         dport = devm_cxl_add_dport(root_port, match, uid, ctx.chbcr);
285         if (IS_ERR(dport)) {
286                 dev_err(host, "failed to add downstream port: %s\n",
287                         dev_name(match));
288                 return PTR_ERR(dport);
289         }
290         dev_dbg(host, "add dport%llu: %s\n", uid, dev_name(match));
291         return 0;
292 }
293
294 static int add_root_nvdimm_bridge(struct device *match, void *data)
295 {
296         struct cxl_decoder *cxld;
297         struct cxl_port *root_port = data;
298         struct cxl_nvdimm_bridge *cxl_nvb;
299         struct device *host = root_port->dev.parent;
300
301         if (!is_root_decoder(match))
302                 return 0;
303
304         cxld = to_cxl_decoder(match);
305         if (!(cxld->flags & CXL_DECODER_F_PMEM))
306                 return 0;
307
308         cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port);
309         if (IS_ERR(cxl_nvb)) {
310                 dev_dbg(host, "failed to register pmem\n");
311                 return PTR_ERR(cxl_nvb);
312         }
313         dev_dbg(host, "%s: add: %s\n", dev_name(&root_port->dev),
314                 dev_name(&cxl_nvb->dev));
315         return 1;
316 }
317
318 static struct lock_class_key cxl_root_key;
319
320 static void cxl_acpi_lock_reset_class(void *dev)
321 {
322         device_lock_reset_class(dev);
323 }
324
325 static void del_cxl_resource(struct resource *res)
326 {
327         kfree(res->name);
328         kfree(res);
329 }
330
331 static void cxl_set_public_resource(struct resource *priv, struct resource *pub)
332 {
333         priv->desc = (unsigned long) pub;
334 }
335
336 static struct resource *cxl_get_public_resource(struct resource *priv)
337 {
338         return (struct resource *) priv->desc;
339 }
340
341 static void remove_cxl_resources(void *data)
342 {
343         struct resource *res, *next, *cxl = data;
344
345         for (res = cxl->child; res; res = next) {
346                 struct resource *victim = cxl_get_public_resource(res);
347
348                 next = res->sibling;
349                 remove_resource(res);
350
351                 if (victim) {
352                         remove_resource(victim);
353                         kfree(victim);
354                 }
355
356                 del_cxl_resource(res);
357         }
358 }
359
360 /**
361  * add_cxl_resources() - reflect CXL fixed memory windows in iomem_resource
362  * @cxl_res: A standalone resource tree where each CXL window is a sibling
363  *
364  * Walk each CXL window in @cxl_res and add it to iomem_resource potentially
365  * expanding its boundaries to ensure that any conflicting resources become
366  * children. If a window is expanded it may then conflict with a another window
367  * entry and require the window to be truncated or trimmed. Consider this
368  * situation:
369  *
370  * |-- "CXL Window 0" --||----- "CXL Window 1" -----|
371  * |--------------- "System RAM" -------------|
372  *
373  * ...where platform firmware has established as System RAM resource across 2
374  * windows, but has left some portion of window 1 for dynamic CXL region
375  * provisioning. In this case "Window 0" will span the entirety of the "System
376  * RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end
377  * of that "System RAM" resource.
378  */
379 static int add_cxl_resources(struct resource *cxl_res)
380 {
381         struct resource *res, *new, *next;
382
383         for (res = cxl_res->child; res; res = next) {
384                 new = kzalloc(sizeof(*new), GFP_KERNEL);
385                 if (!new)
386                         return -ENOMEM;
387                 new->name = res->name;
388                 new->start = res->start;
389                 new->end = res->end;
390                 new->flags = IORESOURCE_MEM;
391                 new->desc = IORES_DESC_CXL;
392
393                 /*
394                  * Record the public resource in the private cxl_res tree for
395                  * later removal.
396                  */
397                 cxl_set_public_resource(res, new);
398
399                 insert_resource_expand_to_fit(&iomem_resource, new);
400
401                 next = res->sibling;
402                 while (next && resource_overlaps(new, next)) {
403                         if (resource_contains(new, next)) {
404                                 struct resource *_next = next->sibling;
405
406                                 remove_resource(next);
407                                 del_cxl_resource(next);
408                                 next = _next;
409                         } else
410                                 next->start = new->end + 1;
411                 }
412         }
413         return 0;
414 }
415
416 static int pair_cxl_resource(struct device *dev, void *data)
417 {
418         struct resource *cxl_res = data;
419         struct resource *p;
420
421         if (!is_root_decoder(dev))
422                 return 0;
423
424         for (p = cxl_res->child; p; p = p->sibling) {
425                 struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev);
426                 struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld;
427                 struct resource res = {
428                         .start = cxld->hpa_range.start,
429                         .end = cxld->hpa_range.end,
430                         .flags = IORESOURCE_MEM,
431                 };
432
433                 if (resource_contains(p, &res)) {
434                         cxlrd->res = cxl_get_public_resource(p);
435                         break;
436                 }
437         }
438
439         return 0;
440 }
441
442 static int cxl_acpi_probe(struct platform_device *pdev)
443 {
444         int rc;
445         struct resource *cxl_res;
446         struct cxl_port *root_port;
447         struct device *host = &pdev->dev;
448         struct acpi_device *adev = ACPI_COMPANION(host);
449         struct cxl_cfmws_context ctx;
450
451         device_lock_set_class(&pdev->dev, &cxl_root_key);
452         rc = devm_add_action_or_reset(&pdev->dev, cxl_acpi_lock_reset_class,
453                                       &pdev->dev);
454         if (rc)
455                 return rc;
456
457         cxl_res = devm_kzalloc(host, sizeof(*cxl_res), GFP_KERNEL);
458         if (!cxl_res)
459                 return -ENOMEM;
460         cxl_res->name = "CXL mem";
461         cxl_res->start = 0;
462         cxl_res->end = -1;
463         cxl_res->flags = IORESOURCE_MEM;
464
465         root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
466         if (IS_ERR(root_port))
467                 return PTR_ERR(root_port);
468
469         rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
470                               add_host_bridge_dport);
471         if (rc < 0)
472                 return rc;
473
474         rc = devm_add_action_or_reset(host, remove_cxl_resources, cxl_res);
475         if (rc)
476                 return rc;
477
478         ctx = (struct cxl_cfmws_context) {
479                 .dev = host,
480                 .root_port = root_port,
481                 .cxl_res = cxl_res,
482         };
483         rc = acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, cxl_parse_cfmws, &ctx);
484         if (rc < 0)
485                 return -ENXIO;
486
487         rc = add_cxl_resources(cxl_res);
488         if (rc)
489                 return rc;
490
491         /*
492          * Populate the root decoders with their related iomem resource,
493          * if present
494          */
495         device_for_each_child(&root_port->dev, cxl_res, pair_cxl_resource);
496
497         /*
498          * Root level scanned with host-bridge as dports, now scan host-bridges
499          * for their role as CXL uports to their CXL-capable PCIe Root Ports.
500          */
501         rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
502                               add_host_bridge_uport);
503         if (rc < 0)
504                 return rc;
505
506         if (IS_ENABLED(CONFIG_CXL_PMEM))
507                 rc = device_for_each_child(&root_port->dev, root_port,
508                                            add_root_nvdimm_bridge);
509         if (rc < 0)
510                 return rc;
511
512         /* In case PCI is scanned before ACPI re-trigger memdev attach */
513         return cxl_bus_rescan();
514 }
515
516 static const struct acpi_device_id cxl_acpi_ids[] = {
517         { "ACPI0017" },
518         { },
519 };
520 MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);
521
522 static const struct platform_device_id cxl_test_ids[] = {
523         { "cxl_acpi" },
524         { },
525 };
526 MODULE_DEVICE_TABLE(platform, cxl_test_ids);
527
528 static struct platform_driver cxl_acpi_driver = {
529         .probe = cxl_acpi_probe,
530         .driver = {
531                 .name = KBUILD_MODNAME,
532                 .acpi_match_table = cxl_acpi_ids,
533         },
534         .id_table = cxl_test_ids,
535 };
536
537 module_platform_driver(cxl_acpi_driver);
538 MODULE_LICENSE("GPL v2");
539 MODULE_IMPORT_NS(CXL);
540 MODULE_IMPORT_NS(ACPI);