Skip to content

Commit

Permalink
Merge branch 'for-6.5/cxl-fwupd' into for-6.5/cxl
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
djbw committed Jun 25, 2023
2 parents dcfb706 + f6448cb commit 867eab6
Show file tree
Hide file tree
Showing 6 changed files with 589 additions and 9 deletions.
11 changes: 11 additions & 0 deletions Documentation/ABI/testing/sysfs-bus-cxl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ Description:
all user data areas of the device.


What: /sys/bus/cxl/devices/memX/firmware/
Date: April, 2023
KernelVersion: v6.5
Contact: linux-cxl@vger.kernel.org
Description:
(RW) Firmware uploader mechanism. The different files under
this directory can be used to upload and activate new
firmware for CXL devices. The interfaces under this are
documented in sysfs-class-firmware.


What: /sys/bus/cxl/devices/*/devtype
Date: June, 2021
KernelVersion: v5.14
Expand Down
1 change: 1 addition & 0 deletions drivers/cxl/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ config CXL_PMEM
config CXL_MEM
tristate "CXL: Memory Expansion"
depends on CXL_PCI
select FW_UPLOAD
default CXL_BUS
help
The CXL.mem protocol allows a device to act as a provider of "System
Expand Down
308 changes: 308 additions & 0 deletions drivers/cxl/core/memdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* Copyright(c) 2020 Intel Corporation. */

#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/firmware.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/idr.h>
Expand Down Expand Up @@ -648,6 +649,313 @@ static int cxl_memdev_release_file(struct inode *inode, struct file *file)
return 0;
}

/**
* cxl_mem_get_fw_info - Get Firmware info
* @cxlds: The device data for the operation
*
* Retrieve firmware info for the device specified.
*
* Return: 0 if no error: or the result of the mailbox command.
*
* See CXL-3.0 8.2.9.3.1 Get FW Info
*/
static int cxl_mem_get_fw_info(struct cxl_dev_state *cxlds)
{
struct cxl_mbox_get_fw_info info;
struct cxl_mbox_cmd mbox_cmd;
int rc;

mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_GET_FW_INFO,
.size_out = sizeof(info),
.payload_out = &info,
};

rc = cxl_internal_send_cmd(cxlds, &mbox_cmd);
if (rc < 0)
return rc;

cxlds->fw.num_slots = info.num_slots;
cxlds->fw.cur_slot = FIELD_GET(CXL_FW_INFO_SLOT_INFO_CUR_MASK,
info.slot_info);

return 0;
}

/**
* cxl_mem_activate_fw - Activate Firmware
* @cxlds: The device data for the operation
* @slot: slot number to activate
*
* Activate firmware in a given slot for the device specified.
*
* Return: 0 if no error: or the result of the mailbox command.
*
* See CXL-3.0 8.2.9.3.3 Activate FW
*/
static int cxl_mem_activate_fw(struct cxl_dev_state *cxlds, int slot)
{
struct cxl_mbox_activate_fw activate;
struct cxl_mbox_cmd mbox_cmd;

if (slot == 0 || slot > cxlds->fw.num_slots)
return -EINVAL;

mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_ACTIVATE_FW,
.size_in = sizeof(activate),
.payload_in = &activate,
};

/* Only offline activation supported for now */
activate.action = CXL_FW_ACTIVATE_OFFLINE;
activate.slot = slot;

return cxl_internal_send_cmd(cxlds, &mbox_cmd);
}

/**
* cxl_mem_abort_fw_xfer - Abort an in-progress FW transfer
* @cxlds: The device data for the operation
*
* Abort an in-progress firmware transfer for the device specified.
*
* Return: 0 if no error: or the result of the mailbox command.
*
* See CXL-3.0 8.2.9.3.2 Transfer FW
*/
static int cxl_mem_abort_fw_xfer(struct cxl_dev_state *cxlds)
{
struct cxl_mbox_transfer_fw *transfer;
struct cxl_mbox_cmd mbox_cmd;
int rc;

transfer = kzalloc(struct_size(transfer, data, 0), GFP_KERNEL);
if (!transfer)
return -ENOMEM;

/* Set a 1s poll interval and a total wait time of 30s */
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_TRANSFER_FW,
.size_in = sizeof(*transfer),
.payload_in = transfer,
.poll_interval_ms = 1000,
.poll_count = 30,
};

transfer->action = CXL_FW_TRANSFER_ACTION_ABORT;

rc = cxl_internal_send_cmd(cxlds, &mbox_cmd);
kfree(transfer);
return rc;
}

static void cxl_fw_cleanup(struct fw_upload *fwl)
{
struct cxl_dev_state *cxlds = fwl->dd_handle;

cxlds->fw.next_slot = 0;
}

static int cxl_fw_do_cancel(struct fw_upload *fwl)
{
struct cxl_dev_state *cxlds = fwl->dd_handle;
struct cxl_memdev *cxlmd = cxlds->cxlmd;
int rc;

rc = cxl_mem_abort_fw_xfer(cxlds);
if (rc < 0)
dev_err(&cxlmd->dev, "Error aborting FW transfer: %d\n", rc);

return FW_UPLOAD_ERR_CANCELED;
}

static enum fw_upload_err cxl_fw_prepare(struct fw_upload *fwl, const u8 *data,
u32 size)
{
struct cxl_dev_state *cxlds = fwl->dd_handle;
struct cxl_mbox_transfer_fw *transfer;

if (!size)
return FW_UPLOAD_ERR_INVALID_SIZE;

cxlds->fw.oneshot = struct_size(transfer, data, size) <
cxlds->payload_size;

if (cxl_mem_get_fw_info(cxlds))
return FW_UPLOAD_ERR_HW_ERROR;

/*
* So far no state has been changed, hence no other cleanup is
* necessary. Simply return the cancelled status.
*/
if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state))
return FW_UPLOAD_ERR_CANCELED;

return FW_UPLOAD_ERR_NONE;
}

static enum fw_upload_err cxl_fw_write(struct fw_upload *fwl, const u8 *data,
u32 offset, u32 size, u32 *written)
{
struct cxl_dev_state *cxlds = fwl->dd_handle;
struct cxl_memdev *cxlmd = cxlds->cxlmd;
struct cxl_mbox_transfer_fw *transfer;
struct cxl_mbox_cmd mbox_cmd;
u32 cur_size, remaining;
size_t size_in;
int rc;

*written = 0;

/* Offset has to be aligned to 128B (CXL-3.0 8.2.9.3.2 Table 8-57) */
if (!IS_ALIGNED(offset, CXL_FW_TRANSFER_ALIGNMENT)) {
dev_err(&cxlmd->dev,
"misaligned offset for FW transfer slice (%u)\n",
offset);
return FW_UPLOAD_ERR_RW_ERROR;
}

/*
* Pick transfer size based on cxlds->payload_size
* @size must bw 128-byte aligned, ->payload_size is a power of 2
* starting at 256 bytes, and sizeof(*transfer) is 128.
* These constraints imply that @cur_size will always be 128b aligned.
*/
cur_size = min_t(size_t, size, cxlds->payload_size - sizeof(*transfer));

remaining = size - cur_size;
size_in = struct_size(transfer, data, cur_size);

if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state))
return cxl_fw_do_cancel(fwl);

/*
* Slot numbers are 1-indexed
* cur_slot is the 0-indexed next_slot (i.e. 'cur_slot - 1 + 1')
* Check for rollover using modulo, and 1-index it by adding 1
*/
cxlds->fw.next_slot = (cxlds->fw.cur_slot % cxlds->fw.num_slots) + 1;

/* Do the transfer via mailbox cmd */
transfer = kzalloc(size_in, GFP_KERNEL);
if (!transfer)
return FW_UPLOAD_ERR_RW_ERROR;

transfer->offset = cpu_to_le32(offset / CXL_FW_TRANSFER_ALIGNMENT);
memcpy(transfer->data, data + offset, cur_size);
if (cxlds->fw.oneshot) {
transfer->action = CXL_FW_TRANSFER_ACTION_FULL;
transfer->slot = cxlds->fw.next_slot;
} else {
if (offset == 0) {
transfer->action = CXL_FW_TRANSFER_ACTION_INITIATE;
} else if (remaining == 0) {
transfer->action = CXL_FW_TRANSFER_ACTION_END;
transfer->slot = cxlds->fw.next_slot;
} else {
transfer->action = CXL_FW_TRANSFER_ACTION_CONTINUE;
}
}

mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_TRANSFER_FW,
.size_in = size_in,
.payload_in = transfer,
.poll_interval_ms = 1000,
.poll_count = 30,
};

rc = cxl_internal_send_cmd(cxlds, &mbox_cmd);
if (rc < 0) {
rc = FW_UPLOAD_ERR_RW_ERROR;
goto out_free;
}

*written = cur_size;

/* Activate FW if oneshot or if the last slice was written */
if (cxlds->fw.oneshot || remaining == 0) {
dev_dbg(&cxlmd->dev, "Activating firmware slot: %d\n",
cxlds->fw.next_slot);
rc = cxl_mem_activate_fw(cxlds, cxlds->fw.next_slot);
if (rc < 0) {
dev_err(&cxlmd->dev, "Error activating firmware: %d\n",
rc);
rc = FW_UPLOAD_ERR_HW_ERROR;
goto out_free;
}
}

rc = FW_UPLOAD_ERR_NONE;

out_free:
kfree(transfer);
return rc;
}

static enum fw_upload_err cxl_fw_poll_complete(struct fw_upload *fwl)
{
struct cxl_dev_state *cxlds = fwl->dd_handle;

/*
* cxl_internal_send_cmd() handles background operations synchronously.
* No need to wait for completions here - any errors would've been
* reported and handled during the ->write() call(s).
* Just check if a cancel request was received, and return success.
*/
if (test_and_clear_bit(CXL_FW_CANCEL, cxlds->fw.state))
return cxl_fw_do_cancel(fwl);

return FW_UPLOAD_ERR_NONE;
}

static void cxl_fw_cancel(struct fw_upload *fwl)
{
struct cxl_dev_state *cxlds = fwl->dd_handle;

set_bit(CXL_FW_CANCEL, cxlds->fw.state);
}

static const struct fw_upload_ops cxl_memdev_fw_ops = {
.prepare = cxl_fw_prepare,
.write = cxl_fw_write,
.poll_complete = cxl_fw_poll_complete,
.cancel = cxl_fw_cancel,
.cleanup = cxl_fw_cleanup,
};

static void devm_cxl_remove_fw_upload(void *fwl)
{
firmware_upload_unregister(fwl);
}

int cxl_memdev_setup_fw_upload(struct cxl_dev_state *cxlds)
{
struct device *dev = &cxlds->cxlmd->dev;
struct fw_upload *fwl;
int rc;

if (!test_bit(CXL_MEM_COMMAND_ID_GET_FW_INFO, cxlds->enabled_cmds))
return 0;

fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev),
&cxl_memdev_fw_ops, cxlds);
if (IS_ERR(fwl))
return dev_err_probe(dev, PTR_ERR(fwl),
"Failed to register firmware loader\n");

rc = devm_add_action_or_reset(cxlds->dev, devm_cxl_remove_fw_upload,
fwl);
if (rc)
dev_err(dev,
"Failed to add firmware loader remove action: %d\n",
rc);

return rc;
}
EXPORT_SYMBOL_NS_GPL(cxl_memdev_setup_fw_upload, CXL);

static const struct file_operations cxl_memdev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = cxl_memdev_ioctl,
Expand Down
Loading

0 comments on commit 867eab6

Please sign in to comment.