|
1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | 2 | /* Copyright(c) 2020 Intel Corporation. */ |
3 | 3 |
|
| 4 | +#include <linux/firmware.h> |
4 | 5 | #include <linux/device.h> |
5 | 6 | #include <linux/slab.h> |
6 | 7 | #include <linux/idr.h> |
@@ -542,6 +543,313 @@ static int cxl_memdev_release_file(struct inode *inode, struct file *file) |
542 | 543 | return 0; |
543 | 544 | } |
544 | 545 |
|
| 546 | +/** |
| 547 | + * cxl_mem_get_fw_info - Get Firmware info |
| 548 | + * @cxlds: The device data for the operation |
| 549 | + * |
| 550 | + * Retrieve firmware info for the device specified. |
| 551 | + * |
| 552 | + * Return: 0 if no error: or the result of the mailbox command. |
| 553 | + * |
| 554 | + * See CXL-3.0 8.2.9.3.1 Get FW Info |
| 555 | + */ |
| 556 | +static int cxl_mem_get_fw_info(struct cxl_dev_state *cxlds) |
| 557 | +{ |
| 558 | + struct cxl_mbox_get_fw_info info; |
| 559 | + struct cxl_mbox_cmd mbox_cmd; |
| 560 | + int rc; |
| 561 | + |
| 562 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 563 | + .opcode = CXL_MBOX_OP_GET_FW_INFO, |
| 564 | + .size_out = sizeof(info), |
| 565 | + .payload_out = &info, |
| 566 | + }; |
| 567 | + |
| 568 | + rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 569 | + if (rc < 0) |
| 570 | + return rc; |
| 571 | + |
| 572 | + cxlds->fw.num_slots = info.num_slots; |
| 573 | + cxlds->fw.cur_slot = FIELD_GET(CXL_FW_INFO_SLOT_INFO_CUR_MASK, |
| 574 | + info.slot_info); |
| 575 | + |
| 576 | + return 0; |
| 577 | +} |
| 578 | + |
| 579 | +/** |
| 580 | + * cxl_mem_activate_fw - Activate Firmware |
| 581 | + * @cxlds: The device data for the operation |
| 582 | + * @slot: slot number to activate |
| 583 | + * |
| 584 | + * Activate firmware in a given slot for the device specified. |
| 585 | + * |
| 586 | + * Return: 0 if no error: or the result of the mailbox command. |
| 587 | + * |
| 588 | + * See CXL-3.0 8.2.9.3.3 Activate FW |
| 589 | + */ |
| 590 | +static int cxl_mem_activate_fw(struct cxl_dev_state *cxlds, int slot) |
| 591 | +{ |
| 592 | + struct cxl_mbox_activate_fw activate; |
| 593 | + struct cxl_mbox_cmd mbox_cmd; |
| 594 | + |
| 595 | + if (slot == 0 || slot > cxlds->fw.num_slots) |
| 596 | + return -EINVAL; |
| 597 | + |
| 598 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 599 | + .opcode = CXL_MBOX_OP_ACTIVATE_FW, |
| 600 | + .size_in = sizeof(activate), |
| 601 | + .payload_in = &activate, |
| 602 | + }; |
| 603 | + |
| 604 | + /* Only offline activation supported for now */ |
| 605 | + activate.action = CXL_FW_ACTIVATE_OFFLINE; |
| 606 | + activate.slot = slot; |
| 607 | + |
| 608 | + return cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 609 | +} |
| 610 | + |
| 611 | +/** |
| 612 | + * cxl_mem_abort_fw_xfer - Abort an in-progress FW transfer |
| 613 | + * @cxlds: The device data for the operation |
| 614 | + * |
| 615 | + * Abort an in-progress firmware transfer for the device specified. |
| 616 | + * |
| 617 | + * Return: 0 if no error: or the result of the mailbox command. |
| 618 | + * |
| 619 | + * See CXL-3.0 8.2.9.3.2 Transfer FW |
| 620 | + */ |
| 621 | +static int cxl_mem_abort_fw_xfer(struct cxl_dev_state *cxlds) |
| 622 | +{ |
| 623 | + struct cxl_mbox_transfer_fw *transfer; |
| 624 | + struct cxl_mbox_cmd mbox_cmd; |
| 625 | + int rc; |
| 626 | + |
| 627 | + transfer = kzalloc(struct_size(transfer, data, 0), GFP_KERNEL); |
| 628 | + if (!transfer) |
| 629 | + return -ENOMEM; |
| 630 | + |
| 631 | + /* Set a 1s poll interval and a total wait time of 30s */ |
| 632 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 633 | + .opcode = CXL_MBOX_OP_TRANSFER_FW, |
| 634 | + .size_in = sizeof(*transfer), |
| 635 | + .payload_in = transfer, |
| 636 | + .poll_interval_ms = 1000, |
| 637 | + .poll_count = 30, |
| 638 | + }; |
| 639 | + |
| 640 | + transfer->action = CXL_FW_TRANSFER_ACTION_ABORT; |
| 641 | + |
| 642 | + rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 643 | + kfree(transfer); |
| 644 | + return rc; |
| 645 | +} |
| 646 | + |
| 647 | +static void cxl_fw_cleanup(struct fw_upload *fwl) |
| 648 | +{ |
| 649 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 650 | + |
| 651 | + cxlds->fw.next_slot = 0; |
| 652 | +} |
| 653 | + |
| 654 | +static int cxl_fw_do_cancel(struct fw_upload *fwl) |
| 655 | +{ |
| 656 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 657 | + struct cxl_memdev *cxlmd = cxlds->cxlmd; |
| 658 | + int rc; |
| 659 | + |
| 660 | + rc = cxl_mem_abort_fw_xfer(cxlds); |
| 661 | + if (rc < 0) |
| 662 | + dev_err(&cxlmd->dev, "Error aborting FW transfer: %d\n", rc); |
| 663 | + |
| 664 | + return FW_UPLOAD_ERR_CANCELED; |
| 665 | +} |
| 666 | + |
| 667 | +static enum fw_upload_err cxl_fw_prepare(struct fw_upload *fwl, const u8 *data, |
| 668 | + u32 size) |
| 669 | +{ |
| 670 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 671 | + struct cxl_mbox_transfer_fw *transfer; |
| 672 | + |
| 673 | + if (!size) |
| 674 | + return FW_UPLOAD_ERR_INVALID_SIZE; |
| 675 | + |
| 676 | + cxlds->fw.oneshot = struct_size(transfer, data, size) < |
| 677 | + cxlds->payload_size; |
| 678 | + |
| 679 | + if (cxl_mem_get_fw_info(cxlds)) |
| 680 | + return FW_UPLOAD_ERR_HW_ERROR; |
| 681 | + |
| 682 | + /* |
| 683 | + * So far no state has been changed, hence no other cleanup is |
| 684 | + * necessary. Simply return the cancelled status. |
| 685 | + */ |
| 686 | + if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state)) |
| 687 | + return FW_UPLOAD_ERR_CANCELED; |
| 688 | + |
| 689 | + return FW_UPLOAD_ERR_NONE; |
| 690 | +} |
| 691 | + |
| 692 | +static enum fw_upload_err cxl_fw_write(struct fw_upload *fwl, const u8 *data, |
| 693 | + u32 offset, u32 size, u32 *written) |
| 694 | +{ |
| 695 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 696 | + struct cxl_memdev *cxlmd = cxlds->cxlmd; |
| 697 | + struct cxl_mbox_transfer_fw *transfer; |
| 698 | + struct cxl_mbox_cmd mbox_cmd; |
| 699 | + u32 cur_size, remaining; |
| 700 | + size_t size_in; |
| 701 | + int rc; |
| 702 | + |
| 703 | + *written = 0; |
| 704 | + |
| 705 | + /* Offset has to be aligned to 128B (CXL-3.0 8.2.9.3.2 Table 8-57) */ |
| 706 | + if (!IS_ALIGNED(offset, CXL_FW_TRANSFER_ALIGNMENT)) { |
| 707 | + dev_err(&cxlmd->dev, |
| 708 | + "misaligned offset for FW transfer slice (%u)\n", |
| 709 | + offset); |
| 710 | + return FW_UPLOAD_ERR_RW_ERROR; |
| 711 | + } |
| 712 | + |
| 713 | + /* |
| 714 | + * Pick transfer size based on cxlds->payload_size |
| 715 | + * @size must bw 128-byte aligned, ->payload_size is a power of 2 |
| 716 | + * starting at 256 bytes, and sizeof(*transfer) is 128. |
| 717 | + * These constraints imply that @cur_size will always be 128b aligned. |
| 718 | + */ |
| 719 | + cur_size = min_t(size_t, size, cxlds->payload_size - sizeof(*transfer)); |
| 720 | + |
| 721 | + remaining = size - cur_size; |
| 722 | + size_in = struct_size(transfer, data, cur_size); |
| 723 | + |
| 724 | + if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state)) |
| 725 | + return cxl_fw_do_cancel(fwl); |
| 726 | + |
| 727 | + /* |
| 728 | + * Slot numbers are 1-indexed |
| 729 | + * cur_slot is the 0-indexed next_slot (i.e. 'cur_slot - 1 + 1') |
| 730 | + * Check for rollover using modulo, and 1-index it by adding 1 |
| 731 | + */ |
| 732 | + cxlds->fw.next_slot = (cxlds->fw.cur_slot % cxlds->fw.num_slots) + 1; |
| 733 | + |
| 734 | + /* Do the transfer via mailbox cmd */ |
| 735 | + transfer = kzalloc(size_in, GFP_KERNEL); |
| 736 | + if (!transfer) |
| 737 | + return FW_UPLOAD_ERR_RW_ERROR; |
| 738 | + |
| 739 | + transfer->offset = cpu_to_le32(offset / CXL_FW_TRANSFER_ALIGNMENT); |
| 740 | + memcpy(transfer->data, data + offset, cur_size); |
| 741 | + if (cxlds->fw.oneshot) { |
| 742 | + transfer->action = CXL_FW_TRANSFER_ACTION_FULL; |
| 743 | + transfer->slot = cxlds->fw.next_slot; |
| 744 | + } else { |
| 745 | + if (offset == 0) { |
| 746 | + transfer->action = CXL_FW_TRANSFER_ACTION_INITIATE; |
| 747 | + } else if (remaining == 0) { |
| 748 | + transfer->action = CXL_FW_TRANSFER_ACTION_END; |
| 749 | + transfer->slot = cxlds->fw.next_slot; |
| 750 | + } else { |
| 751 | + transfer->action = CXL_FW_TRANSFER_ACTION_CONTINUE; |
| 752 | + } |
| 753 | + } |
| 754 | + |
| 755 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 756 | + .opcode = CXL_MBOX_OP_TRANSFER_FW, |
| 757 | + .size_in = size_in, |
| 758 | + .payload_in = transfer, |
| 759 | + .poll_interval_ms = 1000, |
| 760 | + .poll_count = 30, |
| 761 | + }; |
| 762 | + |
| 763 | + rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 764 | + if (rc < 0) { |
| 765 | + rc = FW_UPLOAD_ERR_RW_ERROR; |
| 766 | + goto out_free; |
| 767 | + } |
| 768 | + |
| 769 | + *written = cur_size; |
| 770 | + |
| 771 | + /* Activate FW if oneshot or if the last slice was written */ |
| 772 | + if (cxlds->fw.oneshot || remaining == 0) { |
| 773 | + dev_dbg(&cxlmd->dev, "Activating firmware slot: %d\n", |
| 774 | + cxlds->fw.next_slot); |
| 775 | + rc = cxl_mem_activate_fw(cxlds, cxlds->fw.next_slot); |
| 776 | + if (rc < 0) { |
| 777 | + dev_err(&cxlmd->dev, "Error activating firmware: %d\n", |
| 778 | + rc); |
| 779 | + rc = FW_UPLOAD_ERR_HW_ERROR; |
| 780 | + goto out_free; |
| 781 | + } |
| 782 | + } |
| 783 | + |
| 784 | + rc = FW_UPLOAD_ERR_NONE; |
| 785 | + |
| 786 | +out_free: |
| 787 | + kfree(transfer); |
| 788 | + return rc; |
| 789 | +} |
| 790 | + |
| 791 | +static enum fw_upload_err cxl_fw_poll_complete(struct fw_upload *fwl) |
| 792 | +{ |
| 793 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 794 | + |
| 795 | + /* |
| 796 | + * cxl_internal_send_cmd() handles background operations synchronously. |
| 797 | + * No need to wait for completions here - any errors would've been |
| 798 | + * reported and handled during the ->write() call(s). |
| 799 | + * Just check if a cancel request was received, and return success. |
| 800 | + */ |
| 801 | + if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state)) |
| 802 | + return cxl_fw_do_cancel(fwl); |
| 803 | + |
| 804 | + return FW_UPLOAD_ERR_NONE; |
| 805 | +} |
| 806 | + |
| 807 | +static void cxl_fw_cancel(struct fw_upload *fwl) |
| 808 | +{ |
| 809 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 810 | + |
| 811 | + set_bit(CXL_FW_CANCEL, cxlds->fw.state); |
| 812 | +} |
| 813 | + |
| 814 | +static const struct fw_upload_ops cxl_memdev_fw_ops = { |
| 815 | + .prepare = cxl_fw_prepare, |
| 816 | + .write = cxl_fw_write, |
| 817 | + .poll_complete = cxl_fw_poll_complete, |
| 818 | + .cancel = cxl_fw_cancel, |
| 819 | + .cleanup = cxl_fw_cleanup, |
| 820 | +}; |
| 821 | + |
| 822 | +static void devm_cxl_remove_fw_upload(void *fwl) |
| 823 | +{ |
| 824 | + firmware_upload_unregister(fwl); |
| 825 | +} |
| 826 | + |
| 827 | +int cxl_memdev_setup_fw_upload(struct cxl_dev_state *cxlds) |
| 828 | +{ |
| 829 | + struct device *dev = &cxlds->cxlmd->dev; |
| 830 | + struct fw_upload *fwl; |
| 831 | + int rc; |
| 832 | + |
| 833 | + if (!test_bit(CXL_MEM_COMMAND_ID_GET_FW_INFO, cxlds->enabled_cmds)) |
| 834 | + return 0; |
| 835 | + |
| 836 | + fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev), |
| 837 | + &cxl_memdev_fw_ops, cxlds); |
| 838 | + if (IS_ERR(fwl)) |
| 839 | + return dev_err_probe(dev, PTR_ERR(fwl), |
| 840 | + "Failed to register firmware loader\n"); |
| 841 | + |
| 842 | + rc = devm_add_action_or_reset(cxlds->dev, devm_cxl_remove_fw_upload, |
| 843 | + fwl); |
| 844 | + if (rc) |
| 845 | + dev_err(dev, |
| 846 | + "Failed to add firmware loader remove action: %d\n", |
| 847 | + rc); |
| 848 | + |
| 849 | + return rc; |
| 850 | +} |
| 851 | +EXPORT_SYMBOL_NS_GPL(cxl_memdev_setup_fw_upload, CXL); |
| 852 | + |
545 | 853 | static const struct file_operations cxl_memdev_fops = { |
546 | 854 | .owner = THIS_MODULE, |
547 | 855 | .unlocked_ioctl = cxl_memdev_ioctl, |
|
0 commit comments