1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 2015 IBM Corp. |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/module.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/of_address.h> |
11 | #include <linux/of_platform.h> |
12 | |
13 | #include "cxl.h" |
14 | |
15 | |
16 | static const __be32 *read_prop_string(const struct device_node *np, |
17 | const char *prop_name) |
18 | { |
19 | const __be32 *prop; |
20 | |
21 | prop = of_get_property(node: np, name: prop_name, NULL); |
22 | if (cxl_verbose && prop) |
23 | pr_info("%s: %s\n" , prop_name, (char *) prop); |
24 | return prop; |
25 | } |
26 | |
27 | static const __be32 *read_prop_dword(const struct device_node *np, |
28 | const char *prop_name, u32 *val) |
29 | { |
30 | const __be32 *prop; |
31 | |
32 | prop = of_get_property(node: np, name: prop_name, NULL); |
33 | if (prop) |
34 | *val = be32_to_cpu(prop[0]); |
35 | if (cxl_verbose && prop) |
36 | pr_info("%s: %#x (%u)\n" , prop_name, *val, *val); |
37 | return prop; |
38 | } |
39 | |
40 | static const __be64 *read_prop64_dword(const struct device_node *np, |
41 | const char *prop_name, u64 *val) |
42 | { |
43 | const __be64 *prop; |
44 | |
45 | prop = of_get_property(node: np, name: prop_name, NULL); |
46 | if (prop) |
47 | *val = be64_to_cpu(prop[0]); |
48 | if (cxl_verbose && prop) |
49 | pr_info("%s: %#llx (%llu)\n" , prop_name, *val, *val); |
50 | return prop; |
51 | } |
52 | |
53 | |
54 | static int read_handle(struct device_node *np, u64 *handle) |
55 | { |
56 | const __be32 *prop; |
57 | u64 size; |
58 | |
59 | /* Get address and size of the node */ |
60 | prop = of_get_address(dev: np, index: 0, size: &size, NULL); |
61 | if (size) |
62 | return -EINVAL; |
63 | |
64 | /* Helper to read a big number; size is in cells (not bytes) */ |
65 | *handle = of_read_number(cell: prop, size: of_n_addr_cells(np)); |
66 | return 0; |
67 | } |
68 | |
69 | static int read_phys_addr(struct device_node *np, char *prop_name, |
70 | struct cxl_afu *afu) |
71 | { |
72 | int i, len, entry_size, naddr, nsize, type; |
73 | u64 addr, size; |
74 | const __be32 *prop; |
75 | |
76 | naddr = of_n_addr_cells(np); |
77 | nsize = of_n_size_cells(np); |
78 | |
79 | prop = of_get_property(node: np, name: prop_name, lenp: &len); |
80 | if (prop) { |
81 | entry_size = naddr + nsize; |
82 | for (i = 0; i < (len / 4); i += entry_size, prop += entry_size) { |
83 | type = be32_to_cpu(prop[0]); |
84 | addr = of_read_number(cell: prop, size: naddr); |
85 | size = of_read_number(cell: &prop[naddr], size: nsize); |
86 | switch (type) { |
87 | case 0: /* unit address */ |
88 | afu->guest->handle = addr; |
89 | break; |
90 | case 1: /* p2 area */ |
91 | afu->guest->p2n_phys += addr; |
92 | afu->guest->p2n_size = size; |
93 | break; |
94 | case 2: /* problem state area */ |
95 | afu->psn_phys += addr; |
96 | afu->adapter->ps_size = size; |
97 | break; |
98 | default: |
99 | pr_err("Invalid address type %d found in %s property of AFU\n" , |
100 | type, prop_name); |
101 | return -EINVAL; |
102 | } |
103 | if (cxl_verbose) |
104 | pr_info("%s: %#x %#llx (size %#llx)\n" , |
105 | prop_name, type, addr, size); |
106 | } |
107 | } |
108 | return 0; |
109 | } |
110 | |
111 | static int read_vpd(struct cxl *adapter, struct cxl_afu *afu) |
112 | { |
113 | char vpd[256]; |
114 | int rc; |
115 | size_t len = sizeof(vpd); |
116 | |
117 | memset(vpd, 0, len); |
118 | |
119 | if (adapter) |
120 | rc = cxl_guest_read_adapter_vpd(adapter, buf: vpd, len); |
121 | else |
122 | rc = cxl_guest_read_afu_vpd(afu, buf: vpd, len); |
123 | |
124 | if (rc > 0) { |
125 | cxl_dump_debug_buffer(addr: vpd, size: rc); |
126 | rc = 0; |
127 | } |
128 | return rc; |
129 | } |
130 | |
131 | int cxl_of_read_afu_handle(struct cxl_afu *afu, struct device_node *afu_np) |
132 | { |
133 | if (read_handle(np: afu_np, handle: &afu->guest->handle)) |
134 | return -EINVAL; |
135 | pr_devel("AFU handle: 0x%.16llx\n" , afu->guest->handle); |
136 | |
137 | return 0; |
138 | } |
139 | |
140 | int cxl_of_read_afu_properties(struct cxl_afu *afu, struct device_node *np) |
141 | { |
142 | int i, len, rc; |
143 | char *p; |
144 | const __be32 *prop; |
145 | u16 device_id, vendor_id; |
146 | u32 val = 0, class_code; |
147 | |
148 | /* Properties are read in the same order as listed in PAPR */ |
149 | |
150 | if (cxl_verbose) { |
151 | pr_info("Dump of the 'ibm,coherent-platform-function' node properties:\n" ); |
152 | |
153 | prop = of_get_property(node: np, name: "compatible" , lenp: &len); |
154 | i = 0; |
155 | while (i < len) { |
156 | p = (char *) prop + i; |
157 | pr_info("compatible: %s\n" , p); |
158 | i += strlen(p) + 1; |
159 | } |
160 | read_prop_string(np, prop_name: "name" ); |
161 | } |
162 | |
163 | rc = read_phys_addr(np, prop_name: "reg" , afu); |
164 | if (rc) |
165 | return rc; |
166 | |
167 | rc = read_phys_addr(np, prop_name: "assigned-addresses" , afu); |
168 | if (rc) |
169 | return rc; |
170 | |
171 | if (afu->psn_phys == 0) |
172 | afu->psa = false; |
173 | else |
174 | afu->psa = true; |
175 | |
176 | if (cxl_verbose) { |
177 | read_prop_string(np, prop_name: "ibm,loc-code" ); |
178 | read_prop_string(np, prop_name: "device_type" ); |
179 | } |
180 | |
181 | read_prop_dword(np, prop_name: "ibm,#processes" , val: &afu->max_procs_virtualised); |
182 | |
183 | if (cxl_verbose) { |
184 | read_prop_dword(np, prop_name: "ibm,scratchpad-size" , val: &val); |
185 | read_prop_dword(np, prop_name: "ibm,programmable" , val: &val); |
186 | read_prop_string(np, prop_name: "ibm,phandle" ); |
187 | read_vpd(NULL, afu); |
188 | } |
189 | |
190 | read_prop_dword(np, prop_name: "ibm,max-ints-per-process" , val: &afu->guest->max_ints); |
191 | afu->irqs_max = afu->guest->max_ints; |
192 | |
193 | prop = read_prop_dword(np, prop_name: "ibm,min-ints-per-process" , val: &afu->pp_irqs); |
194 | if (prop) { |
195 | /* One extra interrupt for the PSL interrupt is already |
196 | * included. Remove it now to keep only AFU interrupts and |
197 | * match the native case. |
198 | */ |
199 | afu->pp_irqs--; |
200 | } |
201 | |
202 | if (cxl_verbose) { |
203 | read_prop_dword(np, prop_name: "ibm,max-ints" , val: &val); |
204 | read_prop_dword(np, prop_name: "ibm,vpd-size" , val: &val); |
205 | } |
206 | |
207 | read_prop64_dword(np, prop_name: "ibm,error-buffer-size" , val: &afu->eb_len); |
208 | afu->eb_offset = 0; |
209 | |
210 | if (cxl_verbose) |
211 | read_prop_dword(np, prop_name: "ibm,config-record-type" , val: &val); |
212 | |
213 | read_prop64_dword(np, prop_name: "ibm,config-record-size" , val: &afu->crs_len); |
214 | afu->crs_offset = 0; |
215 | |
216 | read_prop_dword(np, prop_name: "ibm,#config-records" , val: &afu->crs_num); |
217 | |
218 | if (cxl_verbose) { |
219 | for (i = 0; i < afu->crs_num; i++) { |
220 | rc = cxl_ops->afu_cr_read16(afu, i, PCI_DEVICE_ID, |
221 | &device_id); |
222 | if (!rc) |
223 | pr_info("record %d - device-id: %#x\n" , |
224 | i, device_id); |
225 | rc = cxl_ops->afu_cr_read16(afu, i, PCI_VENDOR_ID, |
226 | &vendor_id); |
227 | if (!rc) |
228 | pr_info("record %d - vendor-id: %#x\n" , |
229 | i, vendor_id); |
230 | rc = cxl_ops->afu_cr_read32(afu, i, PCI_CLASS_REVISION, |
231 | &class_code); |
232 | if (!rc) { |
233 | class_code >>= 8; |
234 | pr_info("record %d - class-code: %#x\n" , |
235 | i, class_code); |
236 | } |
237 | } |
238 | |
239 | read_prop_dword(np, prop_name: "ibm,function-number" , val: &val); |
240 | read_prop_dword(np, prop_name: "ibm,privileged-function" , val: &val); |
241 | read_prop_dword(np, prop_name: "vendor-id" , val: &val); |
242 | read_prop_dword(np, prop_name: "device-id" , val: &val); |
243 | read_prop_dword(np, prop_name: "revision-id" , val: &val); |
244 | read_prop_dword(np, prop_name: "class-code" , val: &val); |
245 | read_prop_dword(np, prop_name: "subsystem-vendor-id" , val: &val); |
246 | read_prop_dword(np, prop_name: "subsystem-id" , val: &val); |
247 | } |
248 | /* |
249 | * if "ibm,process-mmio" doesn't exist then per-process mmio is |
250 | * not supported |
251 | */ |
252 | val = 0; |
253 | prop = read_prop_dword(np, prop_name: "ibm,process-mmio" , val: &val); |
254 | if (prop && val == 1) |
255 | afu->pp_psa = true; |
256 | else |
257 | afu->pp_psa = false; |
258 | |
259 | if (cxl_verbose) { |
260 | read_prop_dword(np, prop_name: "ibm,supports-aur" , val: &val); |
261 | read_prop_dword(np, prop_name: "ibm,supports-csrp" , val: &val); |
262 | read_prop_dword(np, prop_name: "ibm,supports-prr" , val: &val); |
263 | } |
264 | |
265 | prop = read_prop_dword(np, prop_name: "ibm,function-error-interrupt" , val: &val); |
266 | if (prop) |
267 | afu->serr_hwirq = val; |
268 | |
269 | pr_devel("AFU handle: %#llx\n" , afu->guest->handle); |
270 | pr_devel("p2n_phys: %#llx (size %#llx)\n" , |
271 | afu->guest->p2n_phys, afu->guest->p2n_size); |
272 | pr_devel("psn_phys: %#llx (size %#llx)\n" , |
273 | afu->psn_phys, afu->adapter->ps_size); |
274 | pr_devel("Max number of processes virtualised=%i\n" , |
275 | afu->max_procs_virtualised); |
276 | pr_devel("Per-process irqs min=%i, max=%i\n" , afu->pp_irqs, |
277 | afu->irqs_max); |
278 | pr_devel("Slice error interrupt=%#lx\n" , afu->serr_hwirq); |
279 | |
280 | return 0; |
281 | } |
282 | |
283 | static int read_adapter_irq_config(struct cxl *adapter, struct device_node *np) |
284 | { |
285 | const __be32 *ranges; |
286 | int len, nranges, i; |
287 | struct irq_avail *cur; |
288 | |
289 | ranges = of_get_property(node: np, name: "interrupt-ranges" , lenp: &len); |
290 | if (ranges == NULL || len < (2 * sizeof(int))) |
291 | return -EINVAL; |
292 | |
293 | /* |
294 | * encoded array of two cells per entry, each cell encoded as |
295 | * with encode-int |
296 | */ |
297 | nranges = len / (2 * sizeof(int)); |
298 | if (nranges == 0 || (nranges * 2 * sizeof(int)) != len) |
299 | return -EINVAL; |
300 | |
301 | adapter->guest->irq_avail = kcalloc(n: nranges, size: sizeof(struct irq_avail), |
302 | GFP_KERNEL); |
303 | if (adapter->guest->irq_avail == NULL) |
304 | return -ENOMEM; |
305 | |
306 | adapter->guest->irq_base_offset = be32_to_cpu(ranges[0]); |
307 | for (i = 0; i < nranges; i++) { |
308 | cur = &adapter->guest->irq_avail[i]; |
309 | cur->offset = be32_to_cpu(ranges[i * 2]); |
310 | cur->range = be32_to_cpu(ranges[i * 2 + 1]); |
311 | cur->bitmap = bitmap_zalloc(nbits: cur->range, GFP_KERNEL); |
312 | if (cur->bitmap == NULL) |
313 | goto err; |
314 | if (cur->offset < adapter->guest->irq_base_offset) |
315 | adapter->guest->irq_base_offset = cur->offset; |
316 | if (cxl_verbose) |
317 | pr_info("available IRQ range: %#lx-%#lx (%lu)\n" , |
318 | cur->offset, cur->offset + cur->range - 1, |
319 | cur->range); |
320 | } |
321 | adapter->guest->irq_nranges = nranges; |
322 | spin_lock_init(&adapter->guest->irq_alloc_lock); |
323 | |
324 | return 0; |
325 | err: |
326 | for (i--; i >= 0; i--) { |
327 | cur = &adapter->guest->irq_avail[i]; |
328 | bitmap_free(bitmap: cur->bitmap); |
329 | } |
330 | kfree(objp: adapter->guest->irq_avail); |
331 | adapter->guest->irq_avail = NULL; |
332 | return -ENOMEM; |
333 | } |
334 | |
335 | int cxl_of_read_adapter_handle(struct cxl *adapter, struct device_node *np) |
336 | { |
337 | if (read_handle(np, handle: &adapter->guest->handle)) |
338 | return -EINVAL; |
339 | pr_devel("Adapter handle: 0x%.16llx\n" , adapter->guest->handle); |
340 | |
341 | return 0; |
342 | } |
343 | |
344 | int cxl_of_read_adapter_properties(struct cxl *adapter, struct device_node *np) |
345 | { |
346 | int rc, len, naddr, i; |
347 | char *p; |
348 | const __be32 *prop; |
349 | u32 val = 0; |
350 | |
351 | /* Properties are read in the same order as listed in PAPR */ |
352 | |
353 | naddr = of_n_addr_cells(np); |
354 | |
355 | if (cxl_verbose) { |
356 | pr_info("Dump of the 'ibm,coherent-platform-facility' node properties:\n" ); |
357 | |
358 | read_prop_dword(np, prop_name: "#address-cells" , val: &val); |
359 | read_prop_dword(np, prop_name: "#size-cells" , val: &val); |
360 | |
361 | prop = of_get_property(node: np, name: "compatible" , lenp: &len); |
362 | i = 0; |
363 | while (i < len) { |
364 | p = (char *) prop + i; |
365 | pr_info("compatible: %s\n" , p); |
366 | i += strlen(p) + 1; |
367 | } |
368 | read_prop_string(np, prop_name: "name" ); |
369 | read_prop_string(np, prop_name: "model" ); |
370 | |
371 | prop = of_get_property(node: np, name: "reg" , NULL); |
372 | if (prop) { |
373 | pr_info("reg: addr:%#llx size:%#x\n" , |
374 | of_read_number(prop, naddr), |
375 | be32_to_cpu(prop[naddr])); |
376 | } |
377 | |
378 | read_prop_string(np, prop_name: "ibm,loc-code" ); |
379 | } |
380 | |
381 | if ((rc = read_adapter_irq_config(adapter, np))) |
382 | return rc; |
383 | |
384 | if (cxl_verbose) { |
385 | read_prop_string(np, prop_name: "device_type" ); |
386 | read_prop_string(np, prop_name: "ibm,phandle" ); |
387 | } |
388 | |
389 | prop = read_prop_dword(np, prop_name: "ibm,caia-version" , val: &val); |
390 | if (prop) { |
391 | adapter->caia_major = (val & 0xFF00) >> 8; |
392 | adapter->caia_minor = val & 0xFF; |
393 | } |
394 | |
395 | prop = read_prop_dword(np, prop_name: "ibm,psl-revision" , val: &val); |
396 | if (prop) |
397 | adapter->psl_rev = val; |
398 | |
399 | prop = read_prop_string(np, prop_name: "status" ); |
400 | if (prop) { |
401 | adapter->guest->status = kasprintf(GFP_KERNEL, fmt: "%s" , (char *) prop); |
402 | if (adapter->guest->status == NULL) |
403 | return -ENOMEM; |
404 | } |
405 | |
406 | prop = read_prop_dword(np, prop_name: "vendor-id" , val: &val); |
407 | if (prop) |
408 | adapter->guest->vendor = val; |
409 | |
410 | prop = read_prop_dword(np, prop_name: "device-id" , val: &val); |
411 | if (prop) |
412 | adapter->guest->device = val; |
413 | |
414 | if (cxl_verbose) { |
415 | read_prop_dword(np, prop_name: "ibm,privileged-facility" , val: &val); |
416 | read_prop_dword(np, prop_name: "revision-id" , val: &val); |
417 | read_prop_dword(np, prop_name: "class-code" , val: &val); |
418 | } |
419 | |
420 | prop = read_prop_dword(np, prop_name: "subsystem-vendor-id" , val: &val); |
421 | if (prop) |
422 | adapter->guest->subsystem_vendor = val; |
423 | |
424 | prop = read_prop_dword(np, prop_name: "subsystem-id" , val: &val); |
425 | if (prop) |
426 | adapter->guest->subsystem = val; |
427 | |
428 | if (cxl_verbose) |
429 | read_vpd(adapter, NULL); |
430 | |
431 | return 0; |
432 | } |
433 | |
434 | static void cxl_of_remove(struct platform_device *pdev) |
435 | { |
436 | struct cxl *adapter; |
437 | int afu; |
438 | |
439 | adapter = dev_get_drvdata(dev: &pdev->dev); |
440 | for (afu = 0; afu < adapter->slices; afu++) |
441 | cxl_guest_remove_afu(afu: adapter->afu[afu]); |
442 | |
443 | cxl_guest_remove_adapter(adapter); |
444 | } |
445 | |
446 | static void cxl_of_shutdown(struct platform_device *pdev) |
447 | { |
448 | cxl_of_remove(pdev); |
449 | } |
450 | |
451 | int cxl_of_probe(struct platform_device *pdev) |
452 | { |
453 | struct device_node *np = NULL; |
454 | struct device_node *afu_np = NULL; |
455 | struct cxl *adapter = NULL; |
456 | int ret; |
457 | int slice = 0, slice_ok = 0; |
458 | |
459 | pr_devel("in %s\n" , __func__); |
460 | |
461 | np = pdev->dev.of_node; |
462 | if (np == NULL) |
463 | return -ENODEV; |
464 | |
465 | /* init adapter */ |
466 | adapter = cxl_guest_init_adapter(np, dev: pdev); |
467 | if (IS_ERR(ptr: adapter)) { |
468 | dev_err(&pdev->dev, "guest_init_adapter failed: %li\n" , PTR_ERR(adapter)); |
469 | return PTR_ERR(ptr: adapter); |
470 | } |
471 | |
472 | /* init afu */ |
473 | for_each_child_of_node(np, afu_np) { |
474 | if ((ret = cxl_guest_init_afu(adapter, slice, afu_np))) |
475 | dev_err(&pdev->dev, "AFU %i failed to initialise: %i\n" , |
476 | slice, ret); |
477 | else |
478 | slice_ok++; |
479 | slice++; |
480 | } |
481 | |
482 | if (slice_ok == 0) { |
483 | dev_info(&pdev->dev, "No active AFU" ); |
484 | adapter->slices = 0; |
485 | } |
486 | |
487 | return 0; |
488 | } |
489 | |
490 | static const struct of_device_id cxl_of_match[] = { |
491 | { .compatible = "ibm,coherent-platform-facility" ,}, |
492 | {}, |
493 | }; |
494 | MODULE_DEVICE_TABLE(of, cxl_of_match); |
495 | |
496 | struct platform_driver cxl_of_driver = { |
497 | .driver = { |
498 | .name = "cxl_of" , |
499 | .of_match_table = cxl_of_match, |
500 | .owner = THIS_MODULE |
501 | }, |
502 | .probe = cxl_of_probe, |
503 | .remove_new = cxl_of_remove, |
504 | .shutdown = cxl_of_shutdown, |
505 | }; |
506 | |