Skip to content

Commit 9521875

Browse files
stellarhopperdjbw
authored andcommitted
cxl: add a firmware update mechanism using the sysfs firmware loader
The sysfs based firmware loader mechanism was created to easily allow userspace to upload firmware images to FPGA cards. This also happens to be pretty suitable to create a user-initiated but kernel-controlled firmware update mechanism for CXL devices, using the CXL specified mailbox commands. Since firmware update commands can be long-running, and can be processed in the background by the endpoint device, it is desirable to have the ability to chunk the firmware transfer down to smaller pieces, so that one operation does not monopolize the mailbox, locking out any other long running background commands entirely - e.g. security commands like 'sanitize' or poison scanning operations. The firmware loader mechanism allows a natural way to perform this chunking, as after each mailbox command, that is restricted to the maximum mailbox payload size, the cxl memdev driver relinquishes control back to the fw_loader system and awaits the next chunk of data to transfer. This opens opportunities for other background commands to access the mailbox and send their own slices of background commands. Add the necessary helpers and state tracking to be able to perform the 'Get FW Info', 'Transfer FW', and 'Activate FW' mailbox commands as described in the CXL spec. Wire these up to the firmware loader callbacks, and register with that system to create the memX/firmware/ sysfs ABI. Cc: Davidlohr Bueso <dave@stgolabs.net> Cc: Jonathan Cameron <Jonathan.Cameron@Huawei.com> Cc: Russ Weight <russell.h.weight@intel.com> Cc: Alison Schofield <alison.schofield@intel.com> Cc: Ira Weiny <ira.weiny@intel.com> Cc: Dave Jiang <dave.jiang@intel.com> Cc: Ben Widawsky <bwidawsk@kernel.org> Cc: Dan Williams <dan.j.williams@intel.com> Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> Reviewed-by: Dave Jiang <dave.jiang@intel.com> Signed-off-by: Vishal Verma <vishal.l.verma@intel.com> Link: https://lore.kernel.org/r/20230602-vv-fw_update-v4-1-c6265bd7343b@intel.com Signed-off-by: Dan Williams <dan.j.williams@intel.com>
1 parent ccadf13 commit 9521875

5 files changed

Lines changed: 406 additions & 0 deletions

File tree

Documentation/ABI/testing/sysfs-bus-cxl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ Description:
5858
affinity for this device.
5959

6060

61+
What: /sys/bus/cxl/devices/memX/firmware/
62+
Date: April, 2023
63+
KernelVersion: v6.5
64+
Contact: linux-cxl@vger.kernel.org
65+
Description:
66+
(RW) Firmware uploader mechanism. The different files under
67+
this directory can be used to upload and activate new
68+
firmware for CXL devices. The interfaces under this are
69+
documented in sysfs-class-firmware.
70+
71+
6172
What: /sys/bus/cxl/devices/*/devtype
6273
Date: June, 2021
6374
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
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: GPL-2.0-only
22
/* Copyright(c) 2020 Intel Corporation. */
33

4+
#include <linux/firmware.h>
45
#include <linux/device.h>
56
#include <linux/slab.h>
67
#include <linux/idr.h>
@@ -542,6 +543,313 @@ static int cxl_memdev_release_file(struct inode *inode, struct file *file)
542543
return 0;
543544
}
544545

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+
545853
static const struct file_operations cxl_memdev_fops = {
546854
.owner = THIS_MODULE,
547855
.unlocked_ioctl = cxl_memdev_ioctl,

0 commit comments

Comments
 (0)