Skip to content

Commit 867eab6

Browse files
committed
Merge branch 'for-6.5/cxl-fwupd' into for-6.5/cxl
Add the first typical (non-sanitization) consumer of the new background command infrastructure, firmware update. Given both firmware-update and sanitization were developed in parallel from the common background-command baseline, resolve some minor context conflicts.
2 parents dcfb706 + f6448cb commit 867eab6

6 files changed

Lines changed: 589 additions & 9 deletions

File tree

Documentation/ABI/testing/sysfs-bus-cxl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ Description:
9595
all user data areas of the device.
9696

9797

98+
What: /sys/bus/cxl/devices/memX/firmware/
99+
Date: April, 2023
100+
KernelVersion: v6.5
101+
Contact: linux-cxl@vger.kernel.org
102+
Description:
103+
(RW) Firmware uploader mechanism. The different files under
104+
this directory can be used to upload and activate new
105+
firmware for CXL devices. The interfaces under this are
106+
documented in sysfs-class-firmware.
107+
108+
98109
What: /sys/bus/cxl/devices/*/devtype
99110
Date: June, 2021
100111
KernelVersion: v5.14

drivers/cxl/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ config CXL_PMEM
8282
config CXL_MEM
8383
tristate "CXL: Memory Expansion"
8484
depends on CXL_PCI
85+
select FW_UPLOAD
8586
default CXL_BUS
8687
help
8788
The CXL.mem protocol allows a device to act as a provider of "System

drivers/cxl/core/memdev.c

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* Copyright(c) 2020 Intel Corporation. */
33

44
#include <linux/io-64-nonatomic-lo-hi.h>
5+
#include <linux/firmware.h>
56
#include <linux/device.h>
67
#include <linux/slab.h>
78
#include <linux/idr.h>
@@ -648,6 +649,313 @@ static int cxl_memdev_release_file(struct inode *inode, struct file *file)
648649
return 0;
649650
}
650651

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+
651959
static const struct file_operations cxl_memdev_fops = {
652960
.owner = THIS_MODULE,
653961
.unlocked_ioctl = cxl_memdev_ioctl,

0 commit comments

Comments
 (0)