|
2 | 2 | /* Copyright(c) 2020 Intel Corporation. */ |
3 | 3 |
|
4 | 4 | #include <linux/io-64-nonatomic-lo-hi.h> |
| 5 | +#include <linux/firmware.h> |
5 | 6 | #include <linux/device.h> |
6 | 7 | #include <linux/slab.h> |
7 | 8 | #include <linux/idr.h> |
@@ -648,6 +649,313 @@ static int cxl_memdev_release_file(struct inode *inode, struct file *file) |
648 | 649 | return 0; |
649 | 650 | } |
650 | 651 |
|
| 652 | +/** |
| 653 | + * cxl_mem_get_fw_info - Get Firmware info |
| 654 | + * @cxlds: The device data for the operation |
| 655 | + * |
| 656 | + * Retrieve firmware info for the device specified. |
| 657 | + * |
| 658 | + * Return: 0 if no error: or the result of the mailbox command. |
| 659 | + * |
| 660 | + * See CXL-3.0 8.2.9.3.1 Get FW Info |
| 661 | + */ |
| 662 | +static int cxl_mem_get_fw_info(struct cxl_dev_state *cxlds) |
| 663 | +{ |
| 664 | + struct cxl_mbox_get_fw_info info; |
| 665 | + struct cxl_mbox_cmd mbox_cmd; |
| 666 | + int rc; |
| 667 | + |
| 668 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 669 | + .opcode = CXL_MBOX_OP_GET_FW_INFO, |
| 670 | + .size_out = sizeof(info), |
| 671 | + .payload_out = &info, |
| 672 | + }; |
| 673 | + |
| 674 | + rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 675 | + if (rc < 0) |
| 676 | + return rc; |
| 677 | + |
| 678 | + cxlds->fw.num_slots = info.num_slots; |
| 679 | + cxlds->fw.cur_slot = FIELD_GET(CXL_FW_INFO_SLOT_INFO_CUR_MASK, |
| 680 | + info.slot_info); |
| 681 | + |
| 682 | + return 0; |
| 683 | +} |
| 684 | + |
| 685 | +/** |
| 686 | + * cxl_mem_activate_fw - Activate Firmware |
| 687 | + * @cxlds: The device data for the operation |
| 688 | + * @slot: slot number to activate |
| 689 | + * |
| 690 | + * Activate firmware in a given slot for the device specified. |
| 691 | + * |
| 692 | + * Return: 0 if no error: or the result of the mailbox command. |
| 693 | + * |
| 694 | + * See CXL-3.0 8.2.9.3.3 Activate FW |
| 695 | + */ |
| 696 | +static int cxl_mem_activate_fw(struct cxl_dev_state *cxlds, int slot) |
| 697 | +{ |
| 698 | + struct cxl_mbox_activate_fw activate; |
| 699 | + struct cxl_mbox_cmd mbox_cmd; |
| 700 | + |
| 701 | + if (slot == 0 || slot > cxlds->fw.num_slots) |
| 702 | + return -EINVAL; |
| 703 | + |
| 704 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 705 | + .opcode = CXL_MBOX_OP_ACTIVATE_FW, |
| 706 | + .size_in = sizeof(activate), |
| 707 | + .payload_in = &activate, |
| 708 | + }; |
| 709 | + |
| 710 | + /* Only offline activation supported for now */ |
| 711 | + activate.action = CXL_FW_ACTIVATE_OFFLINE; |
| 712 | + activate.slot = slot; |
| 713 | + |
| 714 | + return cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 715 | +} |
| 716 | + |
| 717 | +/** |
| 718 | + * cxl_mem_abort_fw_xfer - Abort an in-progress FW transfer |
| 719 | + * @cxlds: The device data for the operation |
| 720 | + * |
| 721 | + * Abort an in-progress firmware transfer for the device specified. |
| 722 | + * |
| 723 | + * Return: 0 if no error: or the result of the mailbox command. |
| 724 | + * |
| 725 | + * See CXL-3.0 8.2.9.3.2 Transfer FW |
| 726 | + */ |
| 727 | +static int cxl_mem_abort_fw_xfer(struct cxl_dev_state *cxlds) |
| 728 | +{ |
| 729 | + struct cxl_mbox_transfer_fw *transfer; |
| 730 | + struct cxl_mbox_cmd mbox_cmd; |
| 731 | + int rc; |
| 732 | + |
| 733 | + transfer = kzalloc(struct_size(transfer, data, 0), GFP_KERNEL); |
| 734 | + if (!transfer) |
| 735 | + return -ENOMEM; |
| 736 | + |
| 737 | + /* Set a 1s poll interval and a total wait time of 30s */ |
| 738 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 739 | + .opcode = CXL_MBOX_OP_TRANSFER_FW, |
| 740 | + .size_in = sizeof(*transfer), |
| 741 | + .payload_in = transfer, |
| 742 | + .poll_interval_ms = 1000, |
| 743 | + .poll_count = 30, |
| 744 | + }; |
| 745 | + |
| 746 | + transfer->action = CXL_FW_TRANSFER_ACTION_ABORT; |
| 747 | + |
| 748 | + rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 749 | + kfree(transfer); |
| 750 | + return rc; |
| 751 | +} |
| 752 | + |
| 753 | +static void cxl_fw_cleanup(struct fw_upload *fwl) |
| 754 | +{ |
| 755 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 756 | + |
| 757 | + cxlds->fw.next_slot = 0; |
| 758 | +} |
| 759 | + |
| 760 | +static int cxl_fw_do_cancel(struct fw_upload *fwl) |
| 761 | +{ |
| 762 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 763 | + struct cxl_memdev *cxlmd = cxlds->cxlmd; |
| 764 | + int rc; |
| 765 | + |
| 766 | + rc = cxl_mem_abort_fw_xfer(cxlds); |
| 767 | + if (rc < 0) |
| 768 | + dev_err(&cxlmd->dev, "Error aborting FW transfer: %d\n", rc); |
| 769 | + |
| 770 | + return FW_UPLOAD_ERR_CANCELED; |
| 771 | +} |
| 772 | + |
| 773 | +static enum fw_upload_err cxl_fw_prepare(struct fw_upload *fwl, const u8 *data, |
| 774 | + u32 size) |
| 775 | +{ |
| 776 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 777 | + struct cxl_mbox_transfer_fw *transfer; |
| 778 | + |
| 779 | + if (!size) |
| 780 | + return FW_UPLOAD_ERR_INVALID_SIZE; |
| 781 | + |
| 782 | + cxlds->fw.oneshot = struct_size(transfer, data, size) < |
| 783 | + cxlds->payload_size; |
| 784 | + |
| 785 | + if (cxl_mem_get_fw_info(cxlds)) |
| 786 | + return FW_UPLOAD_ERR_HW_ERROR; |
| 787 | + |
| 788 | + /* |
| 789 | + * So far no state has been changed, hence no other cleanup is |
| 790 | + * necessary. Simply return the cancelled status. |
| 791 | + */ |
| 792 | + if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state)) |
| 793 | + return FW_UPLOAD_ERR_CANCELED; |
| 794 | + |
| 795 | + return FW_UPLOAD_ERR_NONE; |
| 796 | +} |
| 797 | + |
| 798 | +static enum fw_upload_err cxl_fw_write(struct fw_upload *fwl, const u8 *data, |
| 799 | + u32 offset, u32 size, u32 *written) |
| 800 | +{ |
| 801 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 802 | + struct cxl_memdev *cxlmd = cxlds->cxlmd; |
| 803 | + struct cxl_mbox_transfer_fw *transfer; |
| 804 | + struct cxl_mbox_cmd mbox_cmd; |
| 805 | + u32 cur_size, remaining; |
| 806 | + size_t size_in; |
| 807 | + int rc; |
| 808 | + |
| 809 | + *written = 0; |
| 810 | + |
| 811 | + /* Offset has to be aligned to 128B (CXL-3.0 8.2.9.3.2 Table 8-57) */ |
| 812 | + if (!IS_ALIGNED(offset, CXL_FW_TRANSFER_ALIGNMENT)) { |
| 813 | + dev_err(&cxlmd->dev, |
| 814 | + "misaligned offset for FW transfer slice (%u)\n", |
| 815 | + offset); |
| 816 | + return FW_UPLOAD_ERR_RW_ERROR; |
| 817 | + } |
| 818 | + |
| 819 | + /* |
| 820 | + * Pick transfer size based on cxlds->payload_size |
| 821 | + * @size must bw 128-byte aligned, ->payload_size is a power of 2 |
| 822 | + * starting at 256 bytes, and sizeof(*transfer) is 128. |
| 823 | + * These constraints imply that @cur_size will always be 128b aligned. |
| 824 | + */ |
| 825 | + cur_size = min_t(size_t, size, cxlds->payload_size - sizeof(*transfer)); |
| 826 | + |
| 827 | + remaining = size - cur_size; |
| 828 | + size_in = struct_size(transfer, data, cur_size); |
| 829 | + |
| 830 | + if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state)) |
| 831 | + return cxl_fw_do_cancel(fwl); |
| 832 | + |
| 833 | + /* |
| 834 | + * Slot numbers are 1-indexed |
| 835 | + * cur_slot is the 0-indexed next_slot (i.e. 'cur_slot - 1 + 1') |
| 836 | + * Check for rollover using modulo, and 1-index it by adding 1 |
| 837 | + */ |
| 838 | + cxlds->fw.next_slot = (cxlds->fw.cur_slot % cxlds->fw.num_slots) + 1; |
| 839 | + |
| 840 | + /* Do the transfer via mailbox cmd */ |
| 841 | + transfer = kzalloc(size_in, GFP_KERNEL); |
| 842 | + if (!transfer) |
| 843 | + return FW_UPLOAD_ERR_RW_ERROR; |
| 844 | + |
| 845 | + transfer->offset = cpu_to_le32(offset / CXL_FW_TRANSFER_ALIGNMENT); |
| 846 | + memcpy(transfer->data, data + offset, cur_size); |
| 847 | + if (cxlds->fw.oneshot) { |
| 848 | + transfer->action = CXL_FW_TRANSFER_ACTION_FULL; |
| 849 | + transfer->slot = cxlds->fw.next_slot; |
| 850 | + } else { |
| 851 | + if (offset == 0) { |
| 852 | + transfer->action = CXL_FW_TRANSFER_ACTION_INITIATE; |
| 853 | + } else if (remaining == 0) { |
| 854 | + transfer->action = CXL_FW_TRANSFER_ACTION_END; |
| 855 | + transfer->slot = cxlds->fw.next_slot; |
| 856 | + } else { |
| 857 | + transfer->action = CXL_FW_TRANSFER_ACTION_CONTINUE; |
| 858 | + } |
| 859 | + } |
| 860 | + |
| 861 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 862 | + .opcode = CXL_MBOX_OP_TRANSFER_FW, |
| 863 | + .size_in = size_in, |
| 864 | + .payload_in = transfer, |
| 865 | + .poll_interval_ms = 1000, |
| 866 | + .poll_count = 30, |
| 867 | + }; |
| 868 | + |
| 869 | + rc = cxl_internal_send_cmd(cxlds, &mbox_cmd); |
| 870 | + if (rc < 0) { |
| 871 | + rc = FW_UPLOAD_ERR_RW_ERROR; |
| 872 | + goto out_free; |
| 873 | + } |
| 874 | + |
| 875 | + *written = cur_size; |
| 876 | + |
| 877 | + /* Activate FW if oneshot or if the last slice was written */ |
| 878 | + if (cxlds->fw.oneshot || remaining == 0) { |
| 879 | + dev_dbg(&cxlmd->dev, "Activating firmware slot: %d\n", |
| 880 | + cxlds->fw.next_slot); |
| 881 | + rc = cxl_mem_activate_fw(cxlds, cxlds->fw.next_slot); |
| 882 | + if (rc < 0) { |
| 883 | + dev_err(&cxlmd->dev, "Error activating firmware: %d\n", |
| 884 | + rc); |
| 885 | + rc = FW_UPLOAD_ERR_HW_ERROR; |
| 886 | + goto out_free; |
| 887 | + } |
| 888 | + } |
| 889 | + |
| 890 | + rc = FW_UPLOAD_ERR_NONE; |
| 891 | + |
| 892 | +out_free: |
| 893 | + kfree(transfer); |
| 894 | + return rc; |
| 895 | +} |
| 896 | + |
| 897 | +static enum fw_upload_err cxl_fw_poll_complete(struct fw_upload *fwl) |
| 898 | +{ |
| 899 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 900 | + |
| 901 | + /* |
| 902 | + * cxl_internal_send_cmd() handles background operations synchronously. |
| 903 | + * No need to wait for completions here - any errors would've been |
| 904 | + * reported and handled during the ->write() call(s). |
| 905 | + * Just check if a cancel request was received, and return success. |
| 906 | + */ |
| 907 | + if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state)) |
| 908 | + return cxl_fw_do_cancel(fwl); |
| 909 | + |
| 910 | + return FW_UPLOAD_ERR_NONE; |
| 911 | +} |
| 912 | + |
| 913 | +static void cxl_fw_cancel(struct fw_upload *fwl) |
| 914 | +{ |
| 915 | + struct cxl_dev_state *cxlds = fwl->dd_handle; |
| 916 | + |
| 917 | + set_bit(CXL_FW_CANCEL, cxlds->fw.state); |
| 918 | +} |
| 919 | + |
| 920 | +static const struct fw_upload_ops cxl_memdev_fw_ops = { |
| 921 | + .prepare = cxl_fw_prepare, |
| 922 | + .write = cxl_fw_write, |
| 923 | + .poll_complete = cxl_fw_poll_complete, |
| 924 | + .cancel = cxl_fw_cancel, |
| 925 | + .cleanup = cxl_fw_cleanup, |
| 926 | +}; |
| 927 | + |
| 928 | +static void devm_cxl_remove_fw_upload(void *fwl) |
| 929 | +{ |
| 930 | + firmware_upload_unregister(fwl); |
| 931 | +} |
| 932 | + |
| 933 | +int cxl_memdev_setup_fw_upload(struct cxl_dev_state *cxlds) |
| 934 | +{ |
| 935 | + struct device *dev = &cxlds->cxlmd->dev; |
| 936 | + struct fw_upload *fwl; |
| 937 | + int rc; |
| 938 | + |
| 939 | + if (!test_bit(CXL_MEM_COMMAND_ID_GET_FW_INFO, cxlds->enabled_cmds)) |
| 940 | + return 0; |
| 941 | + |
| 942 | + fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev), |
| 943 | + &cxl_memdev_fw_ops, cxlds); |
| 944 | + if (IS_ERR(fwl)) |
| 945 | + return dev_err_probe(dev, PTR_ERR(fwl), |
| 946 | + "Failed to register firmware loader\n"); |
| 947 | + |
| 948 | + rc = devm_add_action_or_reset(cxlds->dev, devm_cxl_remove_fw_upload, |
| 949 | + fwl); |
| 950 | + if (rc) |
| 951 | + dev_err(dev, |
| 952 | + "Failed to add firmware loader remove action: %d\n", |
| 953 | + rc); |
| 954 | + |
| 955 | + return rc; |
| 956 | +} |
| 957 | +EXPORT_SYMBOL_NS_GPL(cxl_memdev_setup_fw_upload, CXL); |
| 958 | + |
651 | 959 | static const struct file_operations cxl_memdev_fops = { |
652 | 960 | .owner = THIS_MODULE, |
653 | 961 | .unlocked_ioctl = cxl_memdev_ioctl, |
|
0 commit comments