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; |
