Commit 867eab65 authored by Dan Williams's avatar Dan Williams
Browse files

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.
parents dcfb7061 f6448cb5
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+308 −0
Original line number Diff line number Diff line
@@ -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>
@@ -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,
+82 −0
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ static inline bool is_cxl_endpoint(struct cxl_port *port)
}

struct cxl_memdev *devm_cxl_add_memdev(struct cxl_dev_state *cxlds);
int cxl_memdev_setup_fw_upload(struct cxl_dev_state *cxlds);
int devm_cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
			 resource_size_t base, resource_size_t len,
			 resource_size_t skipped);
@@ -260,6 +261,84 @@ struct cxl_poison_state {
	struct mutex lock;  /* Protect reads of poison list */
};

/*
 * Get FW Info
 * CXL rev 3.0 section 8.2.9.3.1; Table 8-56
 */
struct cxl_mbox_get_fw_info {
	u8 num_slots;
	u8 slot_info;
	u8 activation_cap;
	u8 reserved[13];
	char slot_1_revision[16];
	char slot_2_revision[16];
	char slot_3_revision[16];
	char slot_4_revision[16];
} __packed;

#define CXL_FW_INFO_SLOT_INFO_CUR_MASK			GENMASK(2, 0)
#define CXL_FW_INFO_SLOT_INFO_NEXT_MASK			GENMASK(5, 3)
#define CXL_FW_INFO_SLOT_INFO_NEXT_SHIFT		3
#define CXL_FW_INFO_ACTIVATION_CAP_HAS_LIVE_ACTIVATE	BIT(0)

/*
 * Transfer FW Input Payload
 * CXL rev 3.0 section 8.2.9.3.2; Table 8-57
 */
struct cxl_mbox_transfer_fw {
	u8 action;
	u8 slot;
	u8 reserved[2];
	__le32 offset;
	u8 reserved2[0x78];
	u8 data[];
} __packed;

#define CXL_FW_TRANSFER_ACTION_FULL	0x0
#define CXL_FW_TRANSFER_ACTION_INITIATE	0x1
#define CXL_FW_TRANSFER_ACTION_CONTINUE	0x2
#define CXL_FW_TRANSFER_ACTION_END	0x3
#define CXL_FW_TRANSFER_ACTION_ABORT	0x4

/*
 * CXL rev 3.0 section 8.2.9.3.2 mandates 128-byte alignment for FW packages
 * and for each part transferred in a Transfer FW command.
 */
#define CXL_FW_TRANSFER_ALIGNMENT	128

/*
 * Activate FW Input Payload
 * CXL rev 3.0 section 8.2.9.3.3; Table 8-58
 */
struct cxl_mbox_activate_fw {
	u8 action;
	u8 slot;
} __packed;

#define CXL_FW_ACTIVATE_ONLINE		0x0
#define CXL_FW_ACTIVATE_OFFLINE		0x1

/* FW state bits */
#define CXL_FW_STATE_BITS		32
#define CXL_FW_CANCEL		BIT(0)

/**
 * struct cxl_fw_state - Firmware upload / activation state
 *
 * @state: fw_uploader state bitmask
 * @oneshot: whether the fw upload fits in a single transfer
 * @num_slots: Number of FW slots available
 * @cur_slot: Slot number currently active
 * @next_slot: Slot number for the new firmware
 */
struct cxl_fw_state {
	DECLARE_BITMAP(state, CXL_FW_STATE_BITS);
	bool oneshot;
	int num_slots;
	int cur_slot;
	int next_slot;
};

/**
 * struct cxl_security_state - Device security state
 *
@@ -314,6 +393,7 @@ struct cxl_security_state {
 * @serial: PCIe Device Serial Number
 * @event: event log driver state
 * @poison: poison driver state info
 * @fw: firmware upload / activation state
 * @mbox_send: @dev specific transport for transmitting mailbox commands
 *
 * See section 8.2.9.5.2 Capacity Configuration and Label Storage for
@@ -354,6 +434,7 @@ struct cxl_dev_state {
	struct cxl_event_state event;
	struct cxl_poison_state poison;
	struct cxl_security_state security;
	struct cxl_fw_state fw;

	struct rcuwait mbox_wait;
	int (*mbox_send)(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd);
@@ -367,6 +448,7 @@ enum cxl_opcode {
	CXL_MBOX_OP_GET_EVT_INT_POLICY	= 0x0102,
	CXL_MBOX_OP_SET_EVT_INT_POLICY	= 0x0103,
	CXL_MBOX_OP_GET_FW_INFO		= 0x0200,
	CXL_MBOX_OP_TRANSFER_FW		= 0x0201,
	CXL_MBOX_OP_ACTIVATE_FW		= 0x0202,
	CXL_MBOX_OP_SET_TIMESTAMP	= 0x0301,
	CXL_MBOX_OP_GET_SUPPORTED_LOGS	= 0x0400,
+4 −0
Original line number Diff line number Diff line
@@ -921,6 +921,10 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
	if (IS_ERR(cxlmd))
		return PTR_ERR(cxlmd);

	rc = cxl_memdev_setup_fw_upload(cxlds);
	if (rc)
		return rc;

	rc = cxl_event_config(host_bridge, cxlds);
	if (rc)
		return rc;
Loading