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> |
79081590 | 9 | #include <linux/node.h> |
f9db85bf | 10 | #include <asm/div64.h> |
af9cae9f | 11 | #include "cxlpci.h" |
4812be97 DW |
12 | #include "cxl.h" |
13 | ||
d5b1a271 RR |
14 | #define CXL_RCRB_SIZE SZ_8K |
15 | ||
f9db85bf AS |
16 | struct cxl_cxims_data { |
17 | int nr_maps; | |
c66650d2 | 18 | u64 xormaps[] __counted_by(nr_maps); |
f9db85bf AS |
19 | }; |
20 | ||
79081590 DJ |
21 | static const guid_t acpi_cxl_qtg_id_guid = |
22 | GUID_INIT(0xF365F9A6, 0xA7DE, 0x4071, | |
23 | 0xA6, 0x6A, 0xB4, 0x0C, 0x0B, 0x4F, 0x8E, 0x52); | |
24 | ||
f9db85bf AS |
25 | /* |
26 | * Find a targets entry (n) in the host bridge interleave list. | |
cbbd05d0 | 27 | * CXL Specification 3.0 Table 9-22 |
f9db85bf AS |
28 | */ |
29 | static int cxl_xor_calc_n(u64 hpa, struct cxl_cxims_data *cximsd, int iw, | |
30 | int ig) | |
31 | { | |
32 | int i = 0, n = 0; | |
33 | u8 eiw; | |
34 | ||
35 | /* IW: 2,4,6,8,12,16 begin building 'n' using xormaps */ | |
36 | if (iw != 3) { | |
37 | for (i = 0; i < cximsd->nr_maps; i++) | |
38 | n |= (hweight64(hpa & cximsd->xormaps[i]) & 1) << i; | |
39 | } | |
40 | /* IW: 3,6,12 add a modulo calculation to 'n' */ | |
41 | if (!is_power_of_2(iw)) { | |
c99b2e8c | 42 | if (ways_to_eiw(iw, &eiw)) |
f9db85bf AS |
43 | return -1; |
44 | hpa &= GENMASK_ULL(51, eiw + ig); | |
45 | n |= do_div(hpa, 3) << i; | |
46 | } | |
47 | return n; | |
48 | } | |
49 | ||
50 | static struct cxl_dport *cxl_hb_xor(struct cxl_root_decoder *cxlrd, int pos) | |
51 | { | |
52 | struct cxl_cxims_data *cximsd = cxlrd->platform_data; | |
53 | struct cxl_switch_decoder *cxlsd = &cxlrd->cxlsd; | |
54 | struct cxl_decoder *cxld = &cxlsd->cxld; | |
55 | int ig = cxld->interleave_granularity; | |
56 | int iw = cxld->interleave_ways; | |
57 | int n = 0; | |
58 | u64 hpa; | |
59 | ||
60 | if (dev_WARN_ONCE(&cxld->dev, | |
61 | cxld->interleave_ways != cxlsd->nr_targets, | |
62 | "misconfigured root decoder\n")) | |
63 | return NULL; | |
64 | ||
65 | hpa = cxlrd->res->start + pos * ig; | |
66 | ||
67 | /* Entry (n) is 0 for no interleave (iw == 1) */ | |
68 | if (iw != 1) | |
69 | n = cxl_xor_calc_n(hpa, cximsd, iw, ig); | |
70 | ||
71 | if (n < 0) | |
72 | return NULL; | |
73 | ||
74 | return cxlrd->cxlsd.target[n]; | |
75 | } | |
76 | ||
77 | struct cxl_cxims_context { | |
78 | struct device *dev; | |
79 | struct cxl_root_decoder *cxlrd; | |
80 | }; | |
81 | ||
82 | static int cxl_parse_cxims(union acpi_subtable_headers *header, void *arg, | |
83 | const unsigned long end) | |
84 | { | |
85 | struct acpi_cedt_cxims *cxims = (struct acpi_cedt_cxims *)header; | |
86 | struct cxl_cxims_context *ctx = arg; | |
87 | struct cxl_root_decoder *cxlrd = ctx->cxlrd; | |
88 | struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld; | |
89 | struct device *dev = ctx->dev; | |
90 | struct cxl_cxims_data *cximsd; | |
91 | unsigned int hbig, nr_maps; | |
92 | int rc; | |
93 | ||
83351ddb | 94 | rc = eig_to_granularity(cxims->hbig, &hbig); |
f9db85bf AS |
95 | if (rc) |
96 | return rc; | |
97 | ||
98 | /* Does this CXIMS entry apply to the given CXL Window? */ | |
99 | if (hbig != cxld->interleave_granularity) | |
100 | return 0; | |
101 | ||
102 | /* IW 1,3 do not use xormaps and skip this parsing entirely */ | |
103 | if (is_power_of_2(cxld->interleave_ways)) | |
104 | /* 2, 4, 8, 16 way */ | |
105 | nr_maps = ilog2(cxld->interleave_ways); | |
106 | else | |
107 | /* 6, 12 way */ | |
108 | nr_maps = ilog2(cxld->interleave_ways / 3); | |
109 | ||
110 | if (cxims->nr_xormaps < nr_maps) { | |
111 | dev_dbg(dev, "CXIMS nr_xormaps[%d] expected[%d]\n", | |
112 | cxims->nr_xormaps, nr_maps); | |
113 | return -ENXIO; | |
114 | } | |
115 | ||
116 | cximsd = devm_kzalloc(dev, struct_size(cximsd, xormaps, nr_maps), | |
117 | GFP_KERNEL); | |
118 | if (!cximsd) | |
119 | return -ENOMEM; | |
c66650d2 | 120 | cximsd->nr_maps = nr_maps; |
f9db85bf AS |
121 | memcpy(cximsd->xormaps, cxims->xormap_list, |
122 | nr_maps * sizeof(*cximsd->xormaps)); | |
f9db85bf AS |
123 | cxlrd->platform_data = cximsd; |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
3e23d17c AS |
128 | static unsigned long cfmws_to_decoder_flags(int restrictions) |
129 | { | |
0909b4e5 | 130 | unsigned long flags = CXL_DECODER_F_ENABLE; |
3e23d17c AS |
131 | |
132 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE2) | |
133 | flags |= CXL_DECODER_F_TYPE2; | |
134 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_TYPE3) | |
135 | flags |= CXL_DECODER_F_TYPE3; | |
136 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_VOLATILE) | |
137 | flags |= CXL_DECODER_F_RAM; | |
138 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_PMEM) | |
139 | flags |= CXL_DECODER_F_PMEM; | |
140 | if (restrictions & ACPI_CEDT_CFMWS_RESTRICT_FIXED) | |
141 | flags |= CXL_DECODER_F_LOCK; | |
142 | ||
143 | return flags; | |
144 | } | |
145 | ||
146 | static int cxl_acpi_cfmws_verify(struct device *dev, | |
147 | struct acpi_cedt_cfmws *cfmws) | |
148 | { | |
419af595 DW |
149 | int rc, expected_len; |
150 | unsigned int ways; | |
3e23d17c | 151 | |
f9db85bf AS |
152 | if (cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_MODULO && |
153 | cfmws->interleave_arithmetic != ACPI_CEDT_CFMWS_ARITHMETIC_XOR) { | |
154 | dev_err(dev, "CFMWS Unknown Interleave Arithmetic: %d\n", | |
155 | cfmws->interleave_arithmetic); | |
3e23d17c AS |
156 | return -EINVAL; |
157 | } | |
158 | ||
159 | if (!IS_ALIGNED(cfmws->base_hpa, SZ_256M)) { | |
160 | dev_err(dev, "CFMWS Base HPA not 256MB aligned\n"); | |
161 | return -EINVAL; | |
162 | } | |
163 | ||
164 | if (!IS_ALIGNED(cfmws->window_size, SZ_256M)) { | |
165 | dev_err(dev, "CFMWS Window Size not 256MB aligned\n"); | |
166 | return -EINVAL; | |
167 | } | |
168 | ||
c99b2e8c | 169 | rc = eiw_to_ways(cfmws->interleave_ways, &ways); |
419af595 DW |
170 | if (rc) { |
171 | dev_err(dev, "CFMWS Interleave Ways (%d) invalid\n", | |
172 | cfmws->interleave_ways); | |
a5c25802 DW |
173 | return -EINVAL; |
174 | } | |
175 | ||
419af595 | 176 | expected_len = struct_size(cfmws, interleave_targets, ways); |
3e23d17c AS |
177 | |
178 | if (cfmws->header.length < expected_len) { | |
179 | dev_err(dev, "CFMWS length %d less than expected %d\n", | |
180 | cfmws->header.length, expected_len); | |
181 | return -EINVAL; | |
182 | } | |
183 | ||
184 | if (cfmws->header.length > expected_len) | |
185 | dev_dbg(dev, "CFMWS length %d greater than expected %d\n", | |
186 | cfmws->header.length, expected_len); | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
8b3b1c0d DW |
191 | /* |
192 | * Note, @dev must be the first member, see 'struct cxl_chbs_context' | |
193 | * and mock_acpi_table_parse_cedt() | |
194 | */ | |
f4ce1f76 DW |
195 | struct cxl_cfmws_context { |
196 | struct device *dev; | |
197 | struct cxl_port *root_port; | |
974854ab DW |
198 | struct resource *cxl_res; |
199 | int id; | |
f4ce1f76 DW |
200 | }; |
201 | ||
79081590 DJ |
202 | /** |
203 | * cxl_acpi_evaluate_qtg_dsm - Retrieve QTG ids via ACPI _DSM | |
204 | * @handle: ACPI handle | |
205 | * @coord: performance access coordinates | |
206 | * @entries: number of QTG IDs to return | |
207 | * @qos_class: int array provided by caller to return QTG IDs | |
208 | * | |
209 | * Return: number of QTG IDs returned, or -errno for errors | |
210 | * | |
211 | * Issue QTG _DSM with accompanied bandwidth and latency data in order to get | |
212 | * the QTG IDs that are suitable for the performance point in order of most | |
213 | * suitable to least suitable. Write back array of QTG IDs and return the | |
214 | * actual number of QTG IDs written back. | |
215 | */ | |
216 | static int | |
217 | cxl_acpi_evaluate_qtg_dsm(acpi_handle handle, struct access_coordinate *coord, | |
218 | int entries, int *qos_class) | |
219 | { | |
220 | union acpi_object *out_obj, *out_buf, *obj; | |
221 | union acpi_object in_array[4] = { | |
222 | [0].integer = { ACPI_TYPE_INTEGER, coord->read_latency }, | |
223 | [1].integer = { ACPI_TYPE_INTEGER, coord->write_latency }, | |
224 | [2].integer = { ACPI_TYPE_INTEGER, coord->read_bandwidth }, | |
225 | [3].integer = { ACPI_TYPE_INTEGER, coord->write_bandwidth }, | |
226 | }; | |
227 | union acpi_object in_obj = { | |
228 | .package = { | |
229 | .type = ACPI_TYPE_PACKAGE, | |
230 | .count = 4, | |
231 | .elements = in_array, | |
232 | }, | |
233 | }; | |
234 | int count, pkg_entries, i; | |
235 | u16 max_qtg; | |
236 | int rc; | |
237 | ||
238 | if (!entries) | |
239 | return -EINVAL; | |
240 | ||
241 | out_obj = acpi_evaluate_dsm(handle, &acpi_cxl_qtg_id_guid, 1, 1, &in_obj); | |
242 | if (!out_obj) | |
243 | return -ENXIO; | |
244 | ||
245 | if (out_obj->type != ACPI_TYPE_PACKAGE) { | |
246 | rc = -ENXIO; | |
247 | goto out; | |
248 | } | |
249 | ||
250 | /* Check Max QTG ID */ | |
251 | obj = &out_obj->package.elements[0]; | |
252 | if (obj->type != ACPI_TYPE_INTEGER) { | |
253 | rc = -ENXIO; | |
254 | goto out; | |
255 | } | |
256 | ||
257 | max_qtg = obj->integer.value; | |
258 | ||
259 | /* It's legal to have 0 QTG entries */ | |
260 | pkg_entries = out_obj->package.count; | |
261 | if (pkg_entries <= 1) { | |
262 | rc = 0; | |
263 | goto out; | |
264 | } | |
265 | ||
266 | /* Retrieve QTG IDs package */ | |
267 | obj = &out_obj->package.elements[1]; | |
268 | if (obj->type != ACPI_TYPE_PACKAGE) { | |
269 | rc = -ENXIO; | |
270 | goto out; | |
271 | } | |
272 | ||
273 | pkg_entries = obj->package.count; | |
274 | count = min(entries, pkg_entries); | |
275 | for (i = 0; i < count; i++) { | |
276 | u16 qtg_id; | |
277 | ||
278 | out_buf = &obj->package.elements[i]; | |
279 | if (out_buf->type != ACPI_TYPE_INTEGER) { | |
280 | rc = -ENXIO; | |
281 | goto out; | |
282 | } | |
283 | ||
284 | qtg_id = out_buf->integer.value; | |
285 | if (qtg_id > max_qtg) | |
286 | pr_warn("QTG ID %u greater than MAX %u\n", | |
287 | qtg_id, max_qtg); | |
288 | ||
289 | qos_class[i] = qtg_id; | |
290 | } | |
291 | rc = count; | |
292 | ||
293 | out: | |
294 | ACPI_FREE(out_obj); | |
295 | return rc; | |
296 | } | |
297 | ||
44cd71ef | 298 | static int cxl_acpi_qos_class(struct cxl_root *cxl_root, |
79081590 DJ |
299 | struct access_coordinate *coord, int entries, |
300 | int *qos_class) | |
301 | { | |
44cd71ef | 302 | struct device *dev = cxl_root->port.uport_dev; |
79081590 | 303 | acpi_handle handle; |
79081590 DJ |
304 | |
305 | if (!dev_is_platform(dev)) | |
306 | return -ENODEV; | |
307 | ||
308 | handle = ACPI_HANDLE(dev); | |
309 | if (!handle) | |
310 | return -ENODEV; | |
311 | ||
312 | return cxl_acpi_evaluate_qtg_dsm(handle, coord, entries, qos_class); | |
313 | } | |
314 | ||
315 | static const struct cxl_root_ops acpi_root_ops = { | |
316 | .qos_class = cxl_acpi_qos_class, | |
317 | }; | |
318 | ||
f4ce1f76 DW |
319 | static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg, |
320 | const unsigned long end) | |
3e23d17c | 321 | { |
a5c25802 | 322 | int target_map[CXL_DECODER_MAX_INTERLEAVE]; |
f4ce1f76 DW |
323 | struct cxl_cfmws_context *ctx = arg; |
324 | struct cxl_port *root_port = ctx->root_port; | |
974854ab | 325 | struct resource *cxl_res = ctx->cxl_res; |
f9db85bf | 326 | struct cxl_cxims_context cxims_ctx; |
0f157c7f | 327 | struct cxl_root_decoder *cxlrd; |
f4ce1f76 | 328 | struct device *dev = ctx->dev; |
3e23d17c | 329 | struct acpi_cedt_cfmws *cfmws; |
f9db85bf | 330 | cxl_calc_hb_fn cxl_calc_hb; |
3e23d17c | 331 | struct cxl_decoder *cxld; |
419af595 | 332 | unsigned int ways, i, ig; |
974854ab | 333 | struct resource *res; |
419af595 | 334 | int rc; |
3e23d17c | 335 | |
f4ce1f76 | 336 | cfmws = (struct acpi_cedt_cfmws *) header; |
3e23d17c | 337 | |
f4ce1f76 DW |
338 | rc = cxl_acpi_cfmws_verify(dev, cfmws); |
339 | if (rc) { | |
340 | dev_err(dev, "CFMWS range %#llx-%#llx not registered\n", | |
341 | cfmws->base_hpa, | |
48667f67 | 342 | cfmws->base_hpa + cfmws->window_size - 1); |
f4ce1f76 | 343 | return 0; |
3e23d17c | 344 | } |
da6aafec | 345 | |
c99b2e8c | 346 | rc = eiw_to_ways(cfmws->interleave_ways, &ways); |
419af595 DW |
347 | if (rc) |
348 | return rc; | |
83351ddb | 349 | rc = eig_to_granularity(cfmws->granularity, &ig); |
419af595 DW |
350 | if (rc) |
351 | return rc; | |
352 | for (i = 0; i < ways; i++) | |
f4ce1f76 | 353 | target_map[i] = cfmws->interleave_targets[i]; |
da6aafec | 354 | |
974854ab DW |
355 | res = kzalloc(sizeof(*res), GFP_KERNEL); |
356 | if (!res) | |
357 | return -ENOMEM; | |
358 | ||
359 | res->name = kasprintf(GFP_KERNEL, "CXL Window %d", ctx->id++); | |
360 | if (!res->name) | |
361 | goto err_name; | |
362 | ||
363 | res->start = cfmws->base_hpa; | |
364 | res->end = cfmws->base_hpa + cfmws->window_size - 1; | |
365 | res->flags = IORESOURCE_MEM; | |
366 | ||
367 | /* add to the local resource tracking to establish a sort order */ | |
368 | rc = insert_resource(cxl_res, res); | |
369 | if (rc) | |
370 | goto err_insert; | |
371 | ||
f9db85bf AS |
372 | if (cfmws->interleave_arithmetic == ACPI_CEDT_CFMWS_ARITHMETIC_MODULO) |
373 | cxl_calc_hb = cxl_hb_modulo; | |
374 | else | |
375 | cxl_calc_hb = cxl_hb_xor; | |
376 | ||
377 | cxlrd = cxl_root_decoder_alloc(root_port, ways, cxl_calc_hb); | |
0f157c7f | 378 | if (IS_ERR(cxlrd)) |
f4ce1f76 | 379 | return 0; |
da6aafec | 380 | |
0f157c7f | 381 | cxld = &cxlrd->cxlsd.cxld; |
f4ce1f76 | 382 | cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions); |
5aa39a91 | 383 | cxld->target_type = CXL_DECODER_HOSTONLYMEM; |
e50fe01e | 384 | cxld->hpa_range = (struct range) { |
974854ab DW |
385 | .start = res->start, |
386 | .end = res->end, | |
e50fe01e | 387 | }; |
419af595 | 388 | cxld->interleave_ways = ways; |
e7748305 DW |
389 | /* |
390 | * Minimize the x1 granularity to advertise support for any | |
391 | * valid region granularity | |
392 | */ | |
393 | if (ways == 1) | |
394 | ig = CXL_DECODER_MIN_GRANULARITY; | |
419af595 | 395 | cxld->interleave_granularity = ig; |
da6aafec | 396 | |
f9db85bf AS |
397 | if (cfmws->interleave_arithmetic == ACPI_CEDT_CFMWS_ARITHMETIC_XOR) { |
398 | if (ways != 1 && ways != 3) { | |
399 | cxims_ctx = (struct cxl_cxims_context) { | |
400 | .dev = dev, | |
401 | .cxlrd = cxlrd, | |
402 | }; | |
403 | rc = acpi_table_parse_cedt(ACPI_CEDT_TYPE_CXIMS, | |
404 | cxl_parse_cxims, &cxims_ctx); | |
405 | if (rc < 0) | |
406 | goto err_xormap; | |
14628aec AS |
407 | if (!cxlrd->platform_data) { |
408 | dev_err(dev, "No CXIMS for HBIG %u\n", ig); | |
409 | rc = -EINVAL; | |
410 | goto err_xormap; | |
411 | } | |
f9db85bf AS |
412 | } |
413 | } | |
529c0a44 DJ |
414 | |
415 | cxlrd->qos_class = cfmws->qtg_id; | |
416 | ||
f4ce1f76 | 417 | rc = cxl_decoder_add(cxld, target_map); |
f9db85bf | 418 | err_xormap: |
f4ce1f76 DW |
419 | if (rc) |
420 | put_device(&cxld->dev); | |
421 | else | |
422 | rc = cxl_decoder_autoremove(dev, cxld); | |
423 | if (rc) { | |
4cf67d3c | 424 | dev_err(dev, "Failed to add decode range: %pr", res); |
91019b5b | 425 | return rc; |
da6aafec | 426 | } |
e50fe01e DW |
427 | dev_dbg(dev, "add: %s node: %d range [%#llx - %#llx]\n", |
428 | dev_name(&cxld->dev), | |
429 | phys_to_target_node(cxld->hpa_range.start), | |
430 | cxld->hpa_range.start, cxld->hpa_range.end); | |
da6aafec | 431 | |
f4ce1f76 | 432 | return 0; |
974854ab DW |
433 | |
434 | err_insert: | |
435 | kfree(res->name); | |
436 | err_name: | |
437 | kfree(res); | |
438 | return -ENOMEM; | |
da6aafec AS |
439 | } |
440 | ||
67dcdd4d DW |
441 | __mock struct acpi_device *to_cxl_host_bridge(struct device *host, |
442 | struct device *dev) | |
7d4b5ca2 DW |
443 | { |
444 | struct acpi_device *adev = to_acpi_device(dev); | |
445 | ||
a7bfaad5 AS |
446 | if (!acpi_pci_find_root(adev->handle)) |
447 | return NULL; | |
448 | ||
7d4b5ca2 DW |
449 | if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0) |
450 | return adev; | |
451 | return NULL; | |
452 | } | |
453 | ||
eb4663b0 | 454 | /* Note, @dev is used by mock_acpi_table_parse_cedt() */ |
f4ce1f76 | 455 | struct cxl_chbs_context { |
814dff9a | 456 | struct device *dev; |
f4ce1f76 | 457 | unsigned long long uid; |
eb4663b0 | 458 | resource_size_t base; |
d5b1a271 | 459 | u32 cxl_version; |
f4ce1f76 DW |
460 | }; |
461 | ||
d02034b4 RR |
462 | static int cxl_get_chbs_iter(union acpi_subtable_headers *header, void *arg, |
463 | const unsigned long end) | |
f4ce1f76 DW |
464 | { |
465 | struct cxl_chbs_context *ctx = arg; | |
466 | struct acpi_cedt_chbs *chbs; | |
467 | ||
d02034b4 | 468 | if (ctx->base != CXL_RESOURCE_NONE) |
f4ce1f76 DW |
469 | return 0; |
470 | ||
471 | chbs = (struct acpi_cedt_chbs *) header; | |
472 | ||
473 | if (ctx->uid != chbs->uid) | |
474 | return 0; | |
d5b1a271 RR |
475 | |
476 | ctx->cxl_version = chbs->cxl_version; | |
d5b1a271 RR |
477 | if (!chbs->base) |
478 | return 0; | |
479 | ||
eb4663b0 RR |
480 | if (chbs->cxl_version == ACPI_CEDT_CHBS_VERSION_CXL11 && |
481 | chbs->length != CXL_RCRB_SIZE) | |
d5b1a271 | 482 | return 0; |
d5b1a271 | 483 | |
eb4663b0 | 484 | ctx->base = chbs->base; |
f4ce1f76 DW |
485 | |
486 | return 0; | |
487 | } | |
488 | ||
d02034b4 RR |
489 | static int cxl_get_chbs(struct device *dev, struct acpi_device *hb, |
490 | struct cxl_chbs_context *ctx) | |
491 | { | |
492 | unsigned long long uid; | |
493 | int rc; | |
494 | ||
495 | rc = acpi_evaluate_integer(hb->handle, METHOD_NAME__UID, NULL, &uid); | |
496 | if (rc != AE_OK) { | |
497 | dev_err(dev, "unable to retrieve _UID\n"); | |
498 | return -ENOENT; | |
d5b1a271 RR |
499 | } |
500 | ||
d02034b4 RR |
501 | dev_dbg(dev, "UID found: %lld\n", uid); |
502 | *ctx = (struct cxl_chbs_context) { | |
503 | .dev = dev, | |
504 | .uid = uid, | |
505 | .base = CXL_RESOURCE_NONE, | |
506 | .cxl_version = UINT_MAX, | |
507 | }; | |
d5b1a271 | 508 | |
d02034b4 | 509 | acpi_table_parse_cedt(ACPI_CEDT_TYPE_CHBS, cxl_get_chbs_iter, ctx); |
f4ce1f76 DW |
510 | |
511 | return 0; | |
512 | } | |
513 | ||
1037b82f DJ |
514 | static int get_genport_coordinates(struct device *dev, struct cxl_dport *dport) |
515 | { | |
516 | struct acpi_device *hb = to_cxl_host_bridge(NULL, dev); | |
517 | u32 uid; | |
518 | int rc; | |
519 | ||
520 | if (kstrtou32(acpi_device_uid(hb), 0, &uid)) | |
521 | return -EINVAL; | |
522 | ||
523 | rc = acpi_get_genport_coordinates(uid, &dport->hb_coord); | |
524 | if (rc < 0) | |
525 | return rc; | |
526 | ||
527 | /* Adjust back to picoseconds from nanoseconds */ | |
528 | dport->hb_coord.read_latency *= 1000; | |
529 | dport->hb_coord.write_latency *= 1000; | |
530 | ||
531 | return 0; | |
532 | } | |
533 | ||
7d4b5ca2 DW |
534 | static int add_host_bridge_dport(struct device *match, void *arg) |
535 | { | |
1037b82f | 536 | int ret; |
1dedb6f3 RR |
537 | acpi_status rc; |
538 | struct device *bridge; | |
98d2d3a2 | 539 | struct cxl_dport *dport; |
f4ce1f76 | 540 | struct cxl_chbs_context ctx; |
1dedb6f3 | 541 | struct acpi_pci_root *pci_root; |
7d4b5ca2 DW |
542 | struct cxl_port *root_port = arg; |
543 | struct device *host = root_port->dev.parent; | |
1dedb6f3 | 544 | struct acpi_device *hb = to_cxl_host_bridge(host, match); |
7d4b5ca2 | 545 | |
1dedb6f3 | 546 | if (!hb) |
7d4b5ca2 DW |
547 | return 0; |
548 | ||
d02034b4 RR |
549 | rc = cxl_get_chbs(match, hb, &ctx); |
550 | if (rc) | |
551 | return rc; | |
f4ce1f76 | 552 | |
d02034b4 | 553 | if (ctx.cxl_version == UINT_MAX) { |
d5b1a271 | 554 | dev_warn(match, "No CHBS found for Host Bridge (UID %lld)\n", |
d02034b4 | 555 | ctx.uid); |
d5b1a271 RR |
556 | return 0; |
557 | } | |
558 | ||
eb4663b0 RR |
559 | if (ctx.base == CXL_RESOURCE_NONE) { |
560 | dev_warn(match, "CHBS invalid for Host Bridge (UID %lld)\n", | |
d02034b4 | 561 | ctx.uid); |
91a45b12 AS |
562 | return 0; |
563 | } | |
da6aafec | 564 | |
1dedb6f3 RR |
565 | pci_root = acpi_pci_find_root(hb->handle); |
566 | bridge = pci_root->bus->bridge; | |
eb4663b0 | 567 | |
d02034b4 RR |
568 | /* |
569 | * In RCH mode, bind the component regs base to the dport. In | |
570 | * VH mode it will be bound to the CXL host bridge's port | |
571 | * object later in add_host_bridge_uport(). | |
572 | */ | |
eb4663b0 | 573 | if (ctx.cxl_version == ACPI_CEDT_CHBS_VERSION_CXL11) { |
d02034b4 RR |
574 | dev_dbg(match, "RCRB found for UID %lld: %pa\n", ctx.uid, |
575 | &ctx.base); | |
576 | dport = devm_cxl_add_rch_dport(root_port, bridge, ctx.uid, | |
577 | ctx.base); | |
eb4663b0 | 578 | } else { |
d02034b4 RR |
579 | dport = devm_cxl_add_dport(root_port, bridge, ctx.uid, |
580 | CXL_RESOURCE_NONE); | |
eb4663b0 RR |
581 | } |
582 | ||
58eef878 | 583 | if (IS_ERR(dport)) |
98d2d3a2 | 584 | return PTR_ERR(dport); |
58eef878 | 585 | |
1037b82f DJ |
586 | ret = get_genport_coordinates(match, dport); |
587 | if (ret) | |
588 | dev_dbg(match, "Failed to get generic port perf coordinates.\n"); | |
589 | ||
7d4b5ca2 DW |
590 | return 0; |
591 | } | |
592 | ||
f44c7b7a RR |
593 | /* |
594 | * A host bridge is a dport to a CFMWS decode and it is a uport to the | |
595 | * dport (PCIe Root Ports) in the host bridge. | |
596 | */ | |
597 | static int add_host_bridge_uport(struct device *match, void *arg) | |
598 | { | |
599 | struct cxl_port *root_port = arg; | |
600 | struct device *host = root_port->dev.parent; | |
601 | struct acpi_device *hb = to_cxl_host_bridge(host, match); | |
602 | struct acpi_pci_root *pci_root; | |
603 | struct cxl_dport *dport; | |
604 | struct cxl_port *port; | |
605 | struct device *bridge; | |
d02034b4 RR |
606 | struct cxl_chbs_context ctx; |
607 | resource_size_t component_reg_phys; | |
f44c7b7a RR |
608 | int rc; |
609 | ||
610 | if (!hb) | |
611 | return 0; | |
612 | ||
613 | pci_root = acpi_pci_find_root(hb->handle); | |
614 | bridge = pci_root->bus->bridge; | |
615 | dport = cxl_find_dport_by_dev(root_port, bridge); | |
616 | if (!dport) { | |
617 | dev_dbg(host, "host bridge expected and not found\n"); | |
618 | return 0; | |
619 | } | |
620 | ||
621 | if (dport->rch) { | |
622 | dev_info(bridge, "host supports CXL (restricted)\n"); | |
623 | return 0; | |
624 | } | |
625 | ||
d02034b4 RR |
626 | rc = cxl_get_chbs(match, hb, &ctx); |
627 | if (rc) | |
628 | return rc; | |
629 | ||
630 | if (ctx.cxl_version == ACPI_CEDT_CHBS_VERSION_CXL11) { | |
631 | dev_warn(bridge, | |
632 | "CXL CHBS version mismatch, skip port registration\n"); | |
633 | return 0; | |
634 | } | |
635 | ||
636 | component_reg_phys = ctx.base; | |
637 | if (component_reg_phys != CXL_RESOURCE_NONE) | |
638 | dev_dbg(match, "CHBCR found for UID %lld: %pa\n", | |
639 | ctx.uid, &component_reg_phys); | |
640 | ||
f44c7b7a RR |
641 | rc = devm_cxl_register_pci_bus(host, bridge, pci_root->bus); |
642 | if (rc) | |
643 | return rc; | |
644 | ||
d02034b4 | 645 | port = devm_cxl_add_port(host, bridge, component_reg_phys, dport); |
f44c7b7a RR |
646 | if (IS_ERR(port)) |
647 | return PTR_ERR(port); | |
648 | ||
649 | dev_info(bridge, "host supports CXL\n"); | |
650 | ||
651 | return 0; | |
652 | } | |
653 | ||
8fdcb170 DW |
654 | static int add_root_nvdimm_bridge(struct device *match, void *data) |
655 | { | |
656 | struct cxl_decoder *cxld; | |
657 | struct cxl_port *root_port = data; | |
658 | struct cxl_nvdimm_bridge *cxl_nvb; | |
659 | struct device *host = root_port->dev.parent; | |
660 | ||
661 | if (!is_root_decoder(match)) | |
662 | return 0; | |
663 | ||
664 | cxld = to_cxl_decoder(match); | |
665 | if (!(cxld->flags & CXL_DECODER_F_PMEM)) | |
666 | return 0; | |
667 | ||
668 | cxl_nvb = devm_cxl_add_nvdimm_bridge(host, root_port); | |
669 | if (IS_ERR(cxl_nvb)) { | |
670 | dev_dbg(host, "failed to register pmem\n"); | |
671 | return PTR_ERR(cxl_nvb); | |
672 | } | |
673 | dev_dbg(host, "%s: add: %s\n", dev_name(&root_port->dev), | |
674 | dev_name(&cxl_nvb->dev)); | |
675 | return 1; | |
676 | } | |
677 | ||
d864b8ea DW |
678 | static struct lock_class_key cxl_root_key; |
679 | ||
680 | static void cxl_acpi_lock_reset_class(void *dev) | |
681 | { | |
682 | device_lock_reset_class(dev); | |
683 | } | |
684 | ||
974854ab DW |
685 | static void del_cxl_resource(struct resource *res) |
686 | { | |
687 | kfree(res->name); | |
688 | kfree(res); | |
689 | } | |
690 | ||
691 | static void cxl_set_public_resource(struct resource *priv, struct resource *pub) | |
692 | { | |
693 | priv->desc = (unsigned long) pub; | |
694 | } | |
695 | ||
696 | static struct resource *cxl_get_public_resource(struct resource *priv) | |
697 | { | |
698 | return (struct resource *) priv->desc; | |
699 | } | |
700 | ||
701 | static void remove_cxl_resources(void *data) | |
702 | { | |
703 | struct resource *res, *next, *cxl = data; | |
704 | ||
705 | for (res = cxl->child; res; res = next) { | |
706 | struct resource *victim = cxl_get_public_resource(res); | |
707 | ||
708 | next = res->sibling; | |
709 | remove_resource(res); | |
710 | ||
711 | if (victim) { | |
712 | remove_resource(victim); | |
713 | kfree(victim); | |
714 | } | |
715 | ||
716 | del_cxl_resource(res); | |
717 | } | |
718 | } | |
719 | ||
720 | /** | |
721 | * add_cxl_resources() - reflect CXL fixed memory windows in iomem_resource | |
722 | * @cxl_res: A standalone resource tree where each CXL window is a sibling | |
723 | * | |
724 | * Walk each CXL window in @cxl_res and add it to iomem_resource potentially | |
725 | * expanding its boundaries to ensure that any conflicting resources become | |
726 | * children. If a window is expanded it may then conflict with a another window | |
727 | * entry and require the window to be truncated or trimmed. Consider this | |
728 | * situation: | |
729 | * | |
730 | * |-- "CXL Window 0" --||----- "CXL Window 1" -----| | |
731 | * |--------------- "System RAM" -------------| | |
732 | * | |
733 | * ...where platform firmware has established as System RAM resource across 2 | |
734 | * windows, but has left some portion of window 1 for dynamic CXL region | |
735 | * provisioning. In this case "Window 0" will span the entirety of the "System | |
736 | * RAM" span, and "CXL Window 1" is truncated to the remaining tail past the end | |
737 | * of that "System RAM" resource. | |
738 | */ | |
739 | static int add_cxl_resources(struct resource *cxl_res) | |
740 | { | |
741 | struct resource *res, *new, *next; | |
742 | ||
743 | for (res = cxl_res->child; res; res = next) { | |
744 | new = kzalloc(sizeof(*new), GFP_KERNEL); | |
745 | if (!new) | |
746 | return -ENOMEM; | |
747 | new->name = res->name; | |
748 | new->start = res->start; | |
749 | new->end = res->end; | |
750 | new->flags = IORESOURCE_MEM; | |
751 | new->desc = IORES_DESC_CXL; | |
752 | ||
753 | /* | |
754 | * Record the public resource in the private cxl_res tree for | |
755 | * later removal. | |
756 | */ | |
757 | cxl_set_public_resource(res, new); | |
758 | ||
759 | insert_resource_expand_to_fit(&iomem_resource, new); | |
760 | ||
761 | next = res->sibling; | |
762 | while (next && resource_overlaps(new, next)) { | |
763 | if (resource_contains(new, next)) { | |
764 | struct resource *_next = next->sibling; | |
765 | ||
766 | remove_resource(next); | |
767 | del_cxl_resource(next); | |
768 | next = _next; | |
769 | } else | |
770 | next->start = new->end + 1; | |
771 | } | |
772 | } | |
773 | return 0; | |
774 | } | |
775 | ||
0f157c7f DW |
776 | static int pair_cxl_resource(struct device *dev, void *data) |
777 | { | |
778 | struct resource *cxl_res = data; | |
779 | struct resource *p; | |
780 | ||
781 | if (!is_root_decoder(dev)) | |
782 | return 0; | |
783 | ||
784 | for (p = cxl_res->child; p; p = p->sibling) { | |
785 | struct cxl_root_decoder *cxlrd = to_cxl_root_decoder(dev); | |
786 | struct cxl_decoder *cxld = &cxlrd->cxlsd.cxld; | |
787 | struct resource res = { | |
788 | .start = cxld->hpa_range.start, | |
789 | .end = cxld->hpa_range.end, | |
790 | .flags = IORESOURCE_MEM, | |
791 | }; | |
792 | ||
793 | if (resource_contains(p, &res)) { | |
794 | cxlrd->res = cxl_get_public_resource(p); | |
795 | break; | |
796 | } | |
797 | } | |
798 | ||
799 | return 0; | |
800 | } | |
801 | ||
4812be97 DW |
802 | static int cxl_acpi_probe(struct platform_device *pdev) |
803 | { | |
3b94ce7b | 804 | int rc; |
974854ab | 805 | struct resource *cxl_res; |
79081590 | 806 | struct cxl_root *cxl_root; |
4812be97 DW |
807 | struct cxl_port *root_port; |
808 | struct device *host = &pdev->dev; | |
7d4b5ca2 | 809 | struct acpi_device *adev = ACPI_COMPANION(host); |
f4ce1f76 | 810 | struct cxl_cfmws_context ctx; |
4812be97 | 811 | |
d864b8ea DW |
812 | device_lock_set_class(&pdev->dev, &cxl_root_key); |
813 | rc = devm_add_action_or_reset(&pdev->dev, cxl_acpi_lock_reset_class, | |
814 | &pdev->dev); | |
815 | if (rc) | |
816 | return rc; | |
817 | ||
974854ab DW |
818 | cxl_res = devm_kzalloc(host, sizeof(*cxl_res), GFP_KERNEL); |
819 | if (!cxl_res) | |
820 | return -ENOMEM; | |
821 | cxl_res->name = "CXL mem"; | |
822 | cxl_res->start = 0; | |
823 | cxl_res->end = -1; | |
824 | cxl_res->flags = IORESOURCE_MEM; | |
825 | ||
79081590 DJ |
826 | cxl_root = devm_cxl_add_root(host, &acpi_root_ops); |
827 | if (IS_ERR(cxl_root)) | |
828 | return PTR_ERR(cxl_root); | |
829 | root_port = &cxl_root->port; | |
4812be97 | 830 | |
3b94ce7b DW |
831 | rc = bus_for_each_dev(adev->dev.bus, NULL, root_port, |
832 | add_host_bridge_dport); | |
f4ce1f76 DW |
833 | if (rc < 0) |
834 | return rc; | |
3b94ce7b | 835 | |
974854ab DW |
836 | rc = devm_add_action_or_reset(host, remove_cxl_resources, cxl_res); |
837 | if (rc) | |
838 | return rc; | |
839 | ||
f4ce1f76 DW |
840 | ctx = (struct cxl_cfmws_context) { |
841 | .dev = host, | |
842 | .root_port = root_port, | |
974854ab | 843 | .cxl_res = cxl_res, |
f4ce1f76 | 844 | }; |
974854ab DW |
845 | rc = acpi_table_parse_cedt(ACPI_CEDT_TYPE_CFMWS, cxl_parse_cfmws, &ctx); |
846 | if (rc < 0) | |
847 | return -ENXIO; | |
848 | ||
849 | rc = add_cxl_resources(cxl_res); | |
850 | if (rc) | |
851 | return rc; | |
3e23d17c | 852 | |
0f157c7f DW |
853 | /* |
854 | * Populate the root decoders with their related iomem resource, | |
855 | * if present | |
856 | */ | |
857 | device_for_each_child(&root_port->dev, cxl_res, pair_cxl_resource); | |
858 | ||
3b94ce7b DW |
859 | /* |
860 | * Root level scanned with host-bridge as dports, now scan host-bridges | |
861 | * for their role as CXL uports to their CXL-capable PCIe Root Ports. | |
862 | */ | |
8fdcb170 DW |
863 | rc = bus_for_each_dev(adev->dev.bus, NULL, root_port, |
864 | add_host_bridge_uport); | |
f4ce1f76 DW |
865 | if (rc < 0) |
866 | return rc; | |
8fdcb170 DW |
867 | |
868 | if (IS_ENABLED(CONFIG_CXL_PMEM)) | |
869 | rc = device_for_each_child(&root_port->dev, root_port, | |
870 | add_root_nvdimm_bridge); | |
871 | if (rc < 0) | |
872 | return rc; | |
f4ce1f76 | 873 | |
8dd2bc0f | 874 | /* In case PCI is scanned before ACPI re-trigger memdev attach */ |
4029c32f DW |
875 | cxl_bus_rescan(); |
876 | return 0; | |
4812be97 DW |
877 | } |
878 | ||
879 | static const struct acpi_device_id cxl_acpi_ids[] = { | |
f4ce1f76 | 880 | { "ACPI0017" }, |
67dcdd4d | 881 | { }, |
4812be97 DW |
882 | }; |
883 | MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids); | |
884 | ||
a53c28b6 DW |
885 | static const struct platform_device_id cxl_test_ids[] = { |
886 | { "cxl_acpi" }, | |
887 | { }, | |
888 | }; | |
889 | MODULE_DEVICE_TABLE(platform, cxl_test_ids); | |
890 | ||
4812be97 DW |
891 | static struct platform_driver cxl_acpi_driver = { |
892 | .probe = cxl_acpi_probe, | |
893 | .driver = { | |
894 | .name = KBUILD_MODNAME, | |
895 | .acpi_match_table = cxl_acpi_ids, | |
896 | }, | |
a53c28b6 | 897 | .id_table = cxl_test_ids, |
4812be97 DW |
898 | }; |
899 | ||
4029c32f DW |
900 | static int __init cxl_acpi_init(void) |
901 | { | |
902 | return platform_driver_register(&cxl_acpi_driver); | |
903 | } | |
904 | ||
905 | static void __exit cxl_acpi_exit(void) | |
906 | { | |
907 | platform_driver_unregister(&cxl_acpi_driver); | |
908 | cxl_bus_drain(); | |
909 | } | |
910 | ||
09d09e04 DW |
911 | /* load before dax_hmem sees 'Soft Reserved' CXL ranges */ |
912 | subsys_initcall(cxl_acpi_init); | |
4029c32f | 913 | module_exit(cxl_acpi_exit); |
4812be97 DW |
914 | MODULE_LICENSE("GPL v2"); |
915 | MODULE_IMPORT_NS(CXL); | |
f4ce1f76 | 916 | MODULE_IMPORT_NS(ACPI); |