diff options
Diffstat (limited to 'drivers/misc/cxl/sysfs.c')
-rw-r--r-- | drivers/misc/cxl/sysfs.c | 236 |
1 files changed, 226 insertions, 10 deletions
diff --git a/drivers/misc/cxl/sysfs.c b/drivers/misc/cxl/sysfs.c index 461bdbd5d483..d0c38c7bc0c4 100644 --- a/drivers/misc/cxl/sysfs.c +++ b/drivers/misc/cxl/sysfs.c | |||
@@ -10,6 +10,7 @@ | |||
10 | #include <linux/kernel.h> | 10 | #include <linux/kernel.h> |
11 | #include <linux/device.h> | 11 | #include <linux/device.h> |
12 | #include <linux/sysfs.h> | 12 | #include <linux/sysfs.h> |
13 | #include <linux/pci_regs.h> | ||
13 | 14 | ||
14 | #include "cxl.h" | 15 | #include "cxl.h" |
15 | 16 | ||
@@ -56,11 +57,68 @@ static ssize_t image_loaded_show(struct device *device, | |||
56 | return scnprintf(buf, PAGE_SIZE, "factory\n"); | 57 | return scnprintf(buf, PAGE_SIZE, "factory\n"); |
57 | } | 58 | } |
58 | 59 | ||
60 | static ssize_t reset_adapter_store(struct device *device, | ||
61 | struct device_attribute *attr, | ||
62 | const char *buf, size_t count) | ||
63 | { | ||
64 | struct cxl *adapter = to_cxl_adapter(device); | ||
65 | int rc; | ||
66 | int val; | ||
67 | |||
68 | rc = sscanf(buf, "%i", &val); | ||
69 | if ((rc != 1) || (val != 1)) | ||
70 | return -EINVAL; | ||
71 | |||
72 | if ((rc = cxl_reset(adapter))) | ||
73 | return rc; | ||
74 | return count; | ||
75 | } | ||
76 | |||
77 | static ssize_t load_image_on_perst_show(struct device *device, | ||
78 | struct device_attribute *attr, | ||
79 | char *buf) | ||
80 | { | ||
81 | struct cxl *adapter = to_cxl_adapter(device); | ||
82 | |||
83 | if (!adapter->perst_loads_image) | ||
84 | return scnprintf(buf, PAGE_SIZE, "none\n"); | ||
85 | |||
86 | if (adapter->perst_select_user) | ||
87 | return scnprintf(buf, PAGE_SIZE, "user\n"); | ||
88 | return scnprintf(buf, PAGE_SIZE, "factory\n"); | ||
89 | } | ||
90 | |||
91 | static ssize_t load_image_on_perst_store(struct device *device, | ||
92 | struct device_attribute *attr, | ||
93 | const char *buf, size_t count) | ||
94 | { | ||
95 | struct cxl *adapter = to_cxl_adapter(device); | ||
96 | int rc; | ||
97 | |||
98 | if (!strncmp(buf, "none", 4)) | ||
99 | adapter->perst_loads_image = false; | ||
100 | else if (!strncmp(buf, "user", 4)) { | ||
101 | adapter->perst_select_user = true; | ||
102 | adapter->perst_loads_image = true; | ||
103 | } else if (!strncmp(buf, "factory", 7)) { | ||
104 | adapter->perst_select_user = false; | ||
105 | adapter->perst_loads_image = true; | ||
106 | } else | ||
107 | return -EINVAL; | ||
108 | |||
109 | if ((rc = cxl_update_image_control(adapter))) | ||
110 | return rc; | ||
111 | |||
112 | return count; | ||
113 | } | ||
114 | |||
59 | static struct device_attribute adapter_attrs[] = { | 115 | static struct device_attribute adapter_attrs[] = { |
60 | __ATTR_RO(caia_version), | 116 | __ATTR_RO(caia_version), |
61 | __ATTR_RO(psl_revision), | 117 | __ATTR_RO(psl_revision), |
62 | __ATTR_RO(base_image), | 118 | __ATTR_RO(base_image), |
63 | __ATTR_RO(image_loaded), | 119 | __ATTR_RO(image_loaded), |
120 | __ATTR_RW(load_image_on_perst), | ||
121 | __ATTR(reset, S_IWUSR, NULL, reset_adapter_store), | ||
64 | }; | 122 | }; |
65 | 123 | ||
66 | 124 | ||
@@ -310,8 +368,6 @@ static struct device_attribute afu_attrs[] = { | |||
310 | __ATTR(reset, S_IWUSR, NULL, reset_store_afu), | 368 | __ATTR(reset, S_IWUSR, NULL, reset_store_afu), |
311 | }; | 369 | }; |
312 | 370 | ||
313 | |||
314 | |||
315 | int cxl_sysfs_adapter_add(struct cxl *adapter) | 371 | int cxl_sysfs_adapter_add(struct cxl *adapter) |
316 | { | 372 | { |
317 | int i, rc; | 373 | int i, rc; |
@@ -334,31 +390,191 @@ void cxl_sysfs_adapter_remove(struct cxl *adapter) | |||
334 | device_remove_file(&adapter->dev, &adapter_attrs[i]); | 390 | device_remove_file(&adapter->dev, &adapter_attrs[i]); |
335 | } | 391 | } |
336 | 392 | ||
393 | struct afu_config_record { | ||
394 | struct kobject kobj; | ||
395 | struct bin_attribute config_attr; | ||
396 | struct list_head list; | ||
397 | int cr; | ||
398 | u16 device; | ||
399 | u16 vendor; | ||
400 | u32 class; | ||
401 | }; | ||
402 | |||
403 | #define to_cr(obj) container_of(obj, struct afu_config_record, kobj) | ||
404 | |||
405 | static ssize_t vendor_show(struct kobject *kobj, | ||
406 | struct kobj_attribute *attr, char *buf) | ||
407 | { | ||
408 | struct afu_config_record *cr = to_cr(kobj); | ||
409 | |||
410 | return scnprintf(buf, PAGE_SIZE, "0x%.4x\n", cr->vendor); | ||
411 | } | ||
412 | |||
413 | static ssize_t device_show(struct kobject *kobj, | ||
414 | struct kobj_attribute *attr, char *buf) | ||
415 | { | ||
416 | struct afu_config_record *cr = to_cr(kobj); | ||
417 | |||
418 | return scnprintf(buf, PAGE_SIZE, "0x%.4x\n", cr->device); | ||
419 | } | ||
420 | |||
421 | static ssize_t class_show(struct kobject *kobj, | ||
422 | struct kobj_attribute *attr, char *buf) | ||
423 | { | ||
424 | struct afu_config_record *cr = to_cr(kobj); | ||
425 | |||
426 | return scnprintf(buf, PAGE_SIZE, "0x%.6x\n", cr->class); | ||
427 | } | ||
428 | |||
429 | static ssize_t afu_read_config(struct file *filp, struct kobject *kobj, | ||
430 | struct bin_attribute *bin_attr, char *buf, | ||
431 | loff_t off, size_t count) | ||
432 | { | ||
433 | struct afu_config_record *cr = to_cr(kobj); | ||
434 | struct cxl_afu *afu = to_cxl_afu(container_of(kobj->parent, struct device, kobj)); | ||
435 | |||
436 | u64 i, j, val, size = afu->crs_len; | ||
437 | |||
438 | if (off > size) | ||
439 | return 0; | ||
440 | if (off + count > size) | ||
441 | count = size - off; | ||
442 | |||
443 | for (i = 0; i < count;) { | ||
444 | val = cxl_afu_cr_read64(afu, cr->cr, off & ~0x7); | ||
445 | for (j = off & 0x7; j < 8 && i < count; i++, j++, off++) | ||
446 | buf[i] = (val >> (j * 8)) & 0xff; | ||
447 | } | ||
448 | |||
449 | return count; | ||
450 | } | ||
451 | |||
452 | static struct kobj_attribute vendor_attribute = | ||
453 | __ATTR_RO(vendor); | ||
454 | static struct kobj_attribute device_attribute = | ||
455 | __ATTR_RO(device); | ||
456 | static struct kobj_attribute class_attribute = | ||
457 | __ATTR_RO(class); | ||
458 | |||
459 | static struct attribute *afu_cr_attrs[] = { | ||
460 | &vendor_attribute.attr, | ||
461 | &device_attribute.attr, | ||
462 | &class_attribute.attr, | ||
463 | NULL, | ||
464 | }; | ||
465 | |||
466 | static void release_afu_config_record(struct kobject *kobj) | ||
467 | { | ||
468 | struct afu_config_record *cr = to_cr(kobj); | ||
469 | |||
470 | kfree(cr); | ||
471 | } | ||
472 | |||
473 | static struct kobj_type afu_config_record_type = { | ||
474 | .sysfs_ops = &kobj_sysfs_ops, | ||
475 | .release = release_afu_config_record, | ||
476 | .default_attrs = afu_cr_attrs, | ||
477 | }; | ||
478 | |||
479 | static struct afu_config_record *cxl_sysfs_afu_new_cr(struct cxl_afu *afu, int cr_idx) | ||
480 | { | ||
481 | struct afu_config_record *cr; | ||
482 | int rc; | ||
483 | |||
484 | cr = kzalloc(sizeof(struct afu_config_record), GFP_KERNEL); | ||
485 | if (!cr) | ||
486 | return ERR_PTR(-ENOMEM); | ||
487 | |||
488 | cr->cr = cr_idx; | ||
489 | cr->device = cxl_afu_cr_read16(afu, cr_idx, PCI_DEVICE_ID); | ||
490 | cr->vendor = cxl_afu_cr_read16(afu, cr_idx, PCI_VENDOR_ID); | ||
491 | cr->class = cxl_afu_cr_read32(afu, cr_idx, PCI_CLASS_REVISION) >> 8; | ||
492 | |||
493 | /* | ||
494 | * Export raw AFU PCIe like config record. For now this is read only by | ||
495 | * root - we can expand that later to be readable by non-root and maybe | ||
496 | * even writable provided we have a good use-case. Once we suport | ||
497 | * exposing AFUs through a virtual PHB they will get that for free from | ||
498 | * Linux' PCI infrastructure, but until then it's not clear that we | ||
499 | * need it for anything since the main use case is just identifying | ||
500 | * AFUs, which can be done via the vendor, device and class attributes. | ||
501 | */ | ||
502 | sysfs_bin_attr_init(&cr->config_attr); | ||
503 | cr->config_attr.attr.name = "config"; | ||
504 | cr->config_attr.attr.mode = S_IRUSR; | ||
505 | cr->config_attr.size = afu->crs_len; | ||
506 | cr->config_attr.read = afu_read_config; | ||
507 | |||
508 | rc = kobject_init_and_add(&cr->kobj, &afu_config_record_type, | ||
509 | &afu->dev.kobj, "cr%i", cr->cr); | ||
510 | if (rc) | ||
511 | goto err; | ||
512 | |||
513 | rc = sysfs_create_bin_file(&cr->kobj, &cr->config_attr); | ||
514 | if (rc) | ||
515 | goto err1; | ||
516 | |||
517 | rc = kobject_uevent(&cr->kobj, KOBJ_ADD); | ||
518 | if (rc) | ||
519 | goto err2; | ||
520 | |||
521 | return cr; | ||
522 | err2: | ||
523 | sysfs_remove_bin_file(&cr->kobj, &cr->config_attr); | ||
524 | err1: | ||
525 | kobject_put(&cr->kobj); | ||
526 | return ERR_PTR(rc); | ||
527 | err: | ||
528 | kfree(cr); | ||
529 | return ERR_PTR(rc); | ||
530 | } | ||
531 | |||
532 | void cxl_sysfs_afu_remove(struct cxl_afu *afu) | ||
533 | { | ||
534 | struct afu_config_record *cr, *tmp; | ||
535 | int i; | ||
536 | |||
537 | for (i = 0; i < ARRAY_SIZE(afu_attrs); i++) | ||
538 | device_remove_file(&afu->dev, &afu_attrs[i]); | ||
539 | |||
540 | list_for_each_entry_safe(cr, tmp, &afu->crs, list) { | ||
541 | sysfs_remove_bin_file(&cr->kobj, &cr->config_attr); | ||
542 | kobject_put(&cr->kobj); | ||
543 | } | ||
544 | } | ||
545 | |||
337 | int cxl_sysfs_afu_add(struct cxl_afu *afu) | 546 | int cxl_sysfs_afu_add(struct cxl_afu *afu) |
338 | { | 547 | { |
548 | struct afu_config_record *cr; | ||
339 | int i, rc; | 549 | int i, rc; |
340 | 550 | ||
551 | INIT_LIST_HEAD(&afu->crs); | ||
552 | |||
341 | for (i = 0; i < ARRAY_SIZE(afu_attrs); i++) { | 553 | for (i = 0; i < ARRAY_SIZE(afu_attrs); i++) { |
342 | if ((rc = device_create_file(&afu->dev, &afu_attrs[i]))) | 554 | if ((rc = device_create_file(&afu->dev, &afu_attrs[i]))) |
343 | goto err; | 555 | goto err; |
344 | } | 556 | } |
345 | 557 | ||
558 | for (i = 0; i < afu->crs_num; i++) { | ||
559 | cr = cxl_sysfs_afu_new_cr(afu, i); | ||
560 | if (IS_ERR(cr)) { | ||
561 | rc = PTR_ERR(cr); | ||
562 | goto err1; | ||
563 | } | ||
564 | list_add(&cr->list, &afu->crs); | ||
565 | } | ||
566 | |||
346 | return 0; | 567 | return 0; |
347 | 568 | ||
569 | err1: | ||
570 | cxl_sysfs_afu_remove(afu); | ||
571 | return rc; | ||
348 | err: | 572 | err: |
349 | for (i--; i >= 0; i--) | 573 | for (i--; i >= 0; i--) |
350 | device_remove_file(&afu->dev, &afu_attrs[i]); | 574 | device_remove_file(&afu->dev, &afu_attrs[i]); |
351 | return rc; | 575 | return rc; |
352 | } | 576 | } |
353 | 577 | ||
354 | void cxl_sysfs_afu_remove(struct cxl_afu *afu) | ||
355 | { | ||
356 | int i; | ||
357 | |||
358 | for (i = 0; i < ARRAY_SIZE(afu_attrs); i++) | ||
359 | device_remove_file(&afu->dev, &afu_attrs[i]); | ||
360 | } | ||
361 | |||
362 | int cxl_sysfs_afu_m_add(struct cxl_afu *afu) | 578 | int cxl_sysfs_afu_m_add(struct cxl_afu *afu) |
363 | { | 579 | { |
364 | int i, rc; | 580 | int i, rc; |