Commit | Line | Data |
---|---|---|
4812be97 DW |
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> | |
3b94ce7b | 8 | #include <linux/pci.h> |
af9cae9f | 9 | #include "cxlpci.h" |
4812be97 DW |
10 | #include "cxl.h" |
11 | ||
3e23d17c AS |
12 | /* Encode defined in CXL 2.0 8.2.5.12.7 HDM Decoder Control Register */ |
13 | #define CFMWS_INTERLEAVE_WAYS(x) (1 << (x)->interleave_ways) | |
14 | #define CFMWS_INTERLEAVE_GRANULARITY(x) ((x)->granularity + 8) | |
15 | ||
16 | static unsigned long cfmws_to_decoder_flags(int restrictions) | |
17 | { | |
0909b4e5 | 18 | unsigned long flags = CXL_DECODER_F_ENABLE; |
3e23d17c AS |
19 | |
20 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE2) | |
21 | flags |= CXL_DECODER_F_TYPE2; | |
22 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE3) | |
23 | flags |= CXL_DECODER_F_TYPE3; | |
24 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE) | |
25 | flags |= CXL_DECODER_F_RAM; | |
26 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_PMEM) | |
27 | flags |= CXL_DECODER_F_PMEM; | |
28 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_FIXED) | |
29 | flags |= CXL_DECODER_F_LOCK; | |
30 | ||
31 | return flags; | |
32 | } | |
33 | ||
34 | static int cxl_acpi_cfmws_verify(struct device *dev, | |
35 | struct acpi_cedt_cfmws *cfmws) | |
36 | { | |
37 | int expected_len; | |
38 | ||
39 | if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) { | |
40 | dev_err(dev, "CFMWS Unsupported Interleave Arithmetic\n"); | |
41 | return -EINVAL; | |
42 | } | |
43 | ||
44 | if (!IS_ALIGNED(cfmws->base_hpa, SZ_256M)) { | |
45 | dev_err(dev, "CFMWS Base HPA not 256MB aligned\n"); | |
46 | return -EINVAL; | |
47 | } | |
48 | ||
49 | if (!IS_ALIGNED(cfmws->window_size, SZ_256M)) { | |
50 | dev_err(dev, "CFMWS Window Size not 256MB aligned\n"); | |
51 | return -EINVAL; | |
52 | } | |
53 | ||
a5c25802 DW |
54 | if (CFMWS_INTERLEAVE_WAYS(cfmws) > CXL_DECODER_MAX_INTERLEAVE) { |
55 | dev_err(dev, "CFMWS Interleave Ways (%d) too large\n", | |
56 | CFMWS_INTERLEAVE_WAYS(cfmws)); | |
57 | return -EINVAL; | |
58 | } | |
59 | ||
3e23d17c AS |
60 | expected_len = struct_size((cfmws), interleave_targets, |
61 | CFMWS_INTERLEAVE_WAYS(cfmws)); | |
62 | ||
63 | if (cfmws->header.length < expected_len) { | |
64 | dev_err(dev, "CFMWS length %d less than expected %d\n", | |
65 | cfmws->header.length, expected_len); | |
66 | return -EINVAL; | |
67 | } | |
68 | ||
69 | if (cfmws->header.length > expected_len) | |
70 | dev_dbg(dev, "CFMWS length %d greater than expected %d\n", | |
71 | cfmws->header.length, expected_len); | |
72 | ||
73 | return 0; | |
74 | } | |
75 | ||
f4ce1f76 DW |
76 | struct cxl_cfmws_context { |
77 | struct device *dev; | |
78 | struct cxl_port *root_port; | |
79 | }; | |
80 | ||
81 | static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg, | |
82 | const unsigned long end) | |
3e23d17c | 83 | { |
a5c25802 | 84 | int target_map[CXL_DECODER_MAX_INTERLEAVE]; |
f4ce1f76 DW |
85 | struct cxl_cfmws_context *ctx = arg; |
86 | struct cxl_port *root_port = ctx->root_port; | |
87 | struct device *dev = ctx->dev; | |
3e23d17c AS |
88 | struct acpi_cedt_cfmws *cfmws; |
89 | struct cxl_decoder *cxld; | |
f4ce1f76 | 90 | int rc, i; |
3e23d17c | 91 | |
f4ce1f76 | 92 | cfmws = (struct acpi_cedt_cfmws *) header; |
3e23d17c | 93 | |
f4ce1f76 DW |
94 | rc = cxl_acpi_cfmws_verify(dev, cfmws); |
95 | if (rc) { | |
96 | dev_err(dev, "CFMWS range %#llx-%#llx not registered\n", | |
97 | cfmws->base_hpa, | |
48667f67 | 98 | cfmws->base_hpa + cfmws->window_size - 1); |
f4ce1f76 | 99 | return 0; |
3e23d17c | 100 | } |
da6aafec | 101 | |
f4ce1f76 DW |
102 | for (i = 0; i < CFMWS_INTERLEAVE_WAYS(cfmws); i++) |
103 | target_map[i] = cfmws->interleave_targets[i]; | |
da6aafec | 104 | |
d54c1bbe | 105 | cxld = cxl_root_decoder_alloc(root_port, CFMWS_INTERLEAVE_WAYS(cfmws)); |
f4ce1f76 DW |
106 | if (IS_ERR(cxld)) |
107 | return 0; | |
da6aafec | 108 | |
f4ce1f76 DW |
109 | cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions); |
110 | cxld->target_type = CXL_DECODER_EXPANDER; | |
608135db BW |
111 | cxld->platform_res = (struct resource)DEFINE_RES_MEM(cfmws->base_hpa, |
112 | cfmws->window_size); | |
f4ce1f76 DW |
113 | cxld->interleave_ways = CFMWS_INTERLEAVE_WAYS(cfmws); |
114 | cxld->interleave_granularity = CFMWS_INTERLEAVE_GRANULARITY(cfmws); | |
da6aafec | 115 | |
f4ce1f76 DW |
116 | rc = cxl_decoder_add(cxld, target_map); |
117 | if (rc) | |
118 | put_device(&cxld->dev); | |
119 | else | |
120 | rc = cxl_decoder_autoremove(dev, cxld); | |
121 | if (rc) { | |
608135db BW |
122 | dev_err(dev, "Failed to add decoder for %pr\n", |
123 | &cxld->platform_res); | |
f4ce1f76 | 124 | return 0; |
da6aafec | 125 | } |
608135db BW |
126 | dev_dbg(dev, "add: %s node: %d range %pr\n", dev_name(&cxld->dev), |
127 | phys_to_target_node(cxld->platform_res.start), | |
128 | &cxld->platform_res); | |
da6aafec | 129 | |
f4ce1f76 | 130 | return 0; |
da6aafec AS |
131 | } |
132 | ||
67dcdd4d DW |
133 | __mock struct acpi_device *to_cxl_host_bridge(struct device *host, |
134 | struct device *dev) | |
7d4b5ca2 DW |
135 | { |
136 | struct acpi_device *adev = to_acpi_device(dev); | |
137 | ||
a7bfaad5 AS |
138 | if (!acpi_pci_find_root(adev->handle)) |
139 | return NULL; | |
140 | ||
7d4b5ca2 DW |
141 | if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0) |
142 | return adev; | |
143 | return NULL; | |
144 | } | |
145 | ||
3b94ce7b DW |
146 | /* |
147 | * A host bridge is a dport to a CFMWS decode and it is a uport to the | |
148 | * dport (PCIe Root Ports) in the host bridge. | |
149 | */ | |
150 | static int add_host_bridge_uport(struct device *match, void *arg) | |
151 | { | |
3b94ce7b DW |
152 | struct cxl_port *root_port = arg; |
153 | struct device *host = root_port->dev.parent; | |
67dcdd4d | 154 | struct acpi_device *bridge = to_cxl_host_bridge(host, match); |
3b94ce7b | 155 | struct acpi_pci_root *pci_root; |
da6aafec | 156 | struct cxl_dport *dport; |
3b94ce7b | 157 | struct cxl_port *port; |
d17d0540 | 158 | int rc; |
3b94ce7b DW |
159 | |
160 | if (!bridge) | |
161 | return 0; | |
162 | ||
2703c16c | 163 | dport = cxl_find_dport_by_dev(root_port, match); |
da6aafec AS |
164 | if (!dport) { |
165 | dev_dbg(host, "host bridge expected and not found\n"); | |
91a45b12 | 166 | return 0; |
da6aafec AS |
167 | } |
168 | ||
5ff7316f DW |
169 | /* |
170 | * Note that this lookup already succeeded in | |
171 | * to_cxl_host_bridge(), so no need to check for failure here | |
172 | */ | |
173 | pci_root = acpi_pci_find_root(bridge->handle); | |
174 | rc = devm_cxl_register_pci_bus(host, match, pci_root->bus); | |
175 | if (rc) | |
176 | return rc; | |
177 | ||
da6aafec AS |
178 | port = devm_cxl_add_port(host, match, dport->component_reg_phys, |
179 | root_port); | |
3b94ce7b DW |
180 | if (IS_ERR(port)) |
181 | return PTR_ERR(port); | |
182 | dev_dbg(host, "%s: add: %s\n", dev_name(match), dev_name(&port->dev)); | |
183 | ||
54cdbf84 | 184 | return 0; |
3b94ce7b DW |
185 | } |
186 | ||
f4ce1f76 | 187 | struct cxl_chbs_context { |
814dff9a | 188 | struct device *dev; |
f4ce1f76 DW |
189 | unsigned long long uid; |
190 | resource_size_t chbcr; | |
191 | }; | |
192 | ||
193 | static int cxl_get_chbcr(union acpi_subtable_headers *header, void *arg, | |
194 | const unsigned long end) | |
195 | { | |
196 | struct cxl_chbs_context *ctx = arg; | |
197 | struct acpi_cedt_chbs *chbs; | |
198 | ||
199 | if (ctx->chbcr) | |
200 | return 0; | |
201 | ||
202 | chbs = (struct acpi_cedt_chbs *) header; | |
203 | ||
204 | if (ctx->uid != chbs->uid) | |
205 | return 0; | |
206 | ctx->chbcr = chbs->base; | |
207 | ||
208 | return 0; | |
209 | } | |
210 | ||
7d4b5ca2 DW |
211 | static int add_host_bridge_dport(struct device *match, void *arg) |
212 | { | |
7d4b5ca2 DW |
213 | acpi_status status; |
214 | unsigned long long uid; | |
98d2d3a2 | 215 | struct cxl_dport *dport; |
f4ce1f76 | 216 | struct cxl_chbs_context ctx; |
7d4b5ca2 DW |
217 | struct cxl_port *root_port = arg; |
218 | struct device *host = root_port->dev.parent; | |
67dcdd4d | 219 | struct acpi_device *bridge = to_cxl_host_bridge(host, match); |
7d4b5ca2 DW |
220 | |
221 | if (!bridge) | |
222 | return 0; | |
223 | ||
224 | status = acpi_evaluate_integer(bridge->handle, METHOD_NAME__UID, NULL, | |
225 | &uid); | |
226 | if (status != AE_OK) { | |
227 | dev_err(host, "unable to retrieve _UID of %s\n", | |
228 | dev_name(match)); | |
229 | return -ENODEV; | |
230 | } | |
231 | ||
f4ce1f76 | 232 | ctx = (struct cxl_chbs_context) { |
814dff9a | 233 | .dev = host, |
f4ce1f76 DW |
234 | .uid = uid, |
235 | }; | |
236 | acpi_table_parse_cedt(ACPI_CEDT_TYPE_CHBS, cxl_get_chbcr, &ctx); | |
237 | ||
238 | if (ctx.chbcr == 0) { | |
91a45b12 AS |
239 | dev_warn(host, "No CHBS found for Host Bridge: %s\n", |
240 | dev_name(match)); | |
241 | return 0; | |
242 | } | |
da6aafec | 243 | |
664bf115 | 244 | dport = devm_cxl_add_dport(root_port, match, uid, ctx.chbcr); |
98d2d3a2 | 245 | if (IS_ERR(dport)) { |
7d4b5ca2 DW |
246 | dev_err(host, "failed to add downstream port: %s\n", |
247 | dev_name(match)); | |
98d2d3a2 | 248 | return PTR_ERR(dport); |
7d4b5ca2 DW |
249 | } |
250 | dev_dbg(host, "add dport%llu: %s\n", uid, dev_name(match)); | |
251 | return 0; | |
252 | } | |
253 | ||
8fdcb170 DW |
254 | static int add_root_nvdimm_bridge(struct device *match, void *data) |
255 | { | |
256 | struct cxl_decoder *cxld; | |
257 | struct cxl_port *root_port = data; | |
258 | struct cxl_nvdimm_bridge *cxl_nvb; | |
259 | struct device *host = root_port->dev.parent; | |
260 | ||
261 | if (!is_root_decoder(match)) | |
262 | return 0; | |
263 | ||
264 | cxld = to_cxl_decoder(match); | |
265 | if (!(cxld->flags & CXL_DECODER_F_PMEM)) | |
266 | return 0; | |
267 | ||
268 | cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port); | |
269 | if (IS_ERR(cxl_nvb)) { | |
270 | dev_dbg(host, "failed to register pmem\n"); | |
271 | return PTR_ERR(cxl_nvb); | |
272 | } | |
273 | dev_dbg(host, "%s: add: %s\n", dev_name(&root_port->dev), | |
274 | dev_name(&cxl_nvb->dev)); | |
275 | return 1; | |
276 | } | |
277 | ||
d864b8ea DW |
278 | static struct lock_class_key cxl_root_key; |
279 | ||
280 | static void cxl_acpi_lock_reset_class(void *dev) | |
281 | { | |
282 | device_lock_reset_class(dev); | |
283 | } | |
284 | ||
4812be97 DW |
285 | static int cxl_acpi_probe(struct platform_device *pdev) |
286 | { | |
3b94ce7b | 287 | int rc; |
4812be97 DW |
288 | struct cxl_port *root_port; |
289 | struct device *host = &pdev->dev; | |
7d4b5ca2 | 290 | struct acpi_device *adev = ACPI_COMPANION(host); |
f4ce1f76 | 291 | struct cxl_cfmws_context ctx; |
4812be97 | 292 | |
d864b8ea DW |
293 | device_lock_set_class(&pdev->dev, &cxl_root_key); |
294 | rc = devm_add_action_or_reset(&pdev->dev, cxl_acpi_lock_reset_class, | |
295 | &pdev->dev); | |
296 | if (rc) | |
297 | return rc; | |
298 | ||
4812be97 DW |
299 | root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL); |
300 | if (IS_ERR(root_port)) | |
301 | return PTR_ERR(root_port); | |
302 | dev_dbg(host, "add: %s\n", dev_name(&root_port->dev)); | |
303 | ||
3b94ce7b DW |
304 | rc = bus_for_each_dev(adev->dev.bus, NULL, root_port, |
305 | add_host_bridge_dport); | |
f4ce1f76 DW |
306 | if (rc < 0) |
307 | return rc; | |
3b94ce7b | 308 | |
f4ce1f76 DW |
309 | ctx = (struct cxl_cfmws_context) { |
310 | .dev = host, | |
311 | .root_port = root_port, | |
312 | }; | |
313 | acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, cxl_parse_cfmws, &ctx); | |
3e23d17c | 314 | |
3b94ce7b DW |
315 | /* |
316 | * Root level scanned with host-bridge as dports, now scan host-bridges | |
317 | * for their role as CXL uports to their CXL-capable PCIe Root Ports. | |
318 | */ | |
8fdcb170 DW |
319 | rc = bus_for_each_dev(adev->dev.bus, NULL, root_port, |
320 | add_host_bridge_uport); | |
f4ce1f76 DW |
321 | if (rc < 0) |
322 | return rc; | |
8fdcb170 DW |
323 | |
324 | if (IS_ENABLED(CONFIG_CXL_PMEM)) | |
325 | rc = device_for_each_child(&root_port->dev, root_port, | |
326 | add_root_nvdimm_bridge); | |
327 | if (rc < 0) | |
328 | return rc; | |
f4ce1f76 | 329 | |
8dd2bc0f BW |
330 | /* In case PCI is scanned before ACPI re-trigger memdev attach */ |
331 | return cxl_bus_rescan(); | |
4812be97 DW |
332 | } |
333 | ||
334 | static const struct acpi_device_id cxl_acpi_ids[] = { | |
f4ce1f76 | 335 | { "ACPI0017" }, |
67dcdd4d | 336 | { }, |
4812be97 DW |
337 | }; |
338 | MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids); | |
339 | ||
340 | static struct platform_driver cxl_acpi_driver = { | |
341 | .probe = cxl_acpi_probe, | |
342 | .driver = { | |
343 | .name = KBUILD_MODNAME, | |
344 | .acpi_match_table = cxl_acpi_ids, | |
345 | }, | |
346 | }; | |
347 | ||
348 | module_platform_driver(cxl_acpi_driver); | |
349 | MODULE_LICENSE("GPL v2"); | |
350 | MODULE_IMPORT_NS(CXL); | |
f4ce1f76 | 351 | MODULE_IMPORT_NS(ACPI); |