diff options
| author | Christophe Lombard <clombard@linux.vnet.ibm.com> | 2016-03-04 12:26:38 +0100 | 
|---|---|---|
| committer | Michael Ellerman <mpe@ellerman.id.au> | 2016-03-09 23:39:56 +1100 | 
| commit | 594ff7d067ca42676e27e2a7b5dcc0ff039d08ca (patch) | |
| tree | 45824c4fb92c9450b86bfbb9ff19a0dd17cd2c93 /drivers/misc/cxl/flash.c | |
| parent | 4752876c71701b7663a5ded789058ab2c05f7d0f (diff) | |
cxl: Support to flash a new image on the adapter from a guest
The new flash.c file contains the logic to flash a new image on the
adapter, through a hcall. It is an iterative process, with chunks of
data of 1M at a time. There are also 2 phases: write and verify. The
flash operation itself is driven from a user-land tool.
Once flashing is successful, an rtas call is made to update the device
tree with the new properties values for the adapter and the AFU(s)
Add a new char device for the adapter, so that the flash tool can
access the card, even if there is no valid AFU on it.
Co-authored-by: Frederic Barrat <fbarrat@linux.vnet.ibm.com>
Signed-off-by: Frederic Barrat <fbarrat@linux.vnet.ibm.com>
Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
Reviewed-by: Manoj Kumar <manoj@linux.vnet.ibm.com>
Acked-by: Ian Munsie <imunsie@au1.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Diffstat (limited to 'drivers/misc/cxl/flash.c')
| -rw-r--r-- | drivers/misc/cxl/flash.c | 538 | 
1 files changed, 538 insertions, 0 deletions
diff --git a/drivers/misc/cxl/flash.c b/drivers/misc/cxl/flash.c new file mode 100644 index 000000000000..68dd0b7da471 --- /dev/null +++ b/drivers/misc/cxl/flash.c @@ -0,0 +1,538 @@ +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/semaphore.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <asm/rtas.h> + +#include "cxl.h" +#include "hcalls.h" + +#define DOWNLOAD_IMAGE 1 +#define VALIDATE_IMAGE 2 + +struct ai_header { +	u16 version; +	u8  reserved0[6]; +	u16 vendor; +	u16 device; +	u16 subsystem_vendor; +	u16 subsystem; +	u64 image_offset; +	u64 image_length; +	u8  reserved1[96]; +}; + +static struct semaphore sem; +unsigned long *buffer[CXL_AI_MAX_ENTRIES]; +struct sg_list *le; +static u64 continue_token; +static unsigned int transfer; + +struct update_props_workarea { +	__be32 phandle; +	__be32 state; +	__be64 reserved; +	__be32 nprops; +} __packed; + +struct update_nodes_workarea { +	__be32 state; +	__be64 unit_address; +	__be32 reserved; +} __packed; + +#define DEVICE_SCOPE 3 +#define NODE_ACTION_MASK	0xff000000 +#define NODE_COUNT_MASK		0x00ffffff +#define OPCODE_DELETE	0x01000000 +#define OPCODE_UPDATE	0x02000000 +#define OPCODE_ADD	0x03000000 + +static int rcall(int token, char *buf, s32 scope) +{ +	int rc; + +	spin_lock(&rtas_data_buf_lock); + +	memcpy(rtas_data_buf, buf, RTAS_DATA_BUF_SIZE); +	rc = rtas_call(token, 2, 1, NULL, rtas_data_buf, scope); +	memcpy(buf, rtas_data_buf, RTAS_DATA_BUF_SIZE); + +	spin_unlock(&rtas_data_buf_lock); +	return rc; +} + +static int update_property(struct device_node *dn, const char *name, +			   u32 vd, char *value) +{ +	struct property *new_prop; +	u32 *val; +	int rc; + +	new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); +	if (!new_prop) +		return -ENOMEM; + +	new_prop->name = kstrdup(name, GFP_KERNEL); +	if (!new_prop->name) { +		kfree(new_prop); +		return -ENOMEM; +	} + +	new_prop->length = vd; +	new_prop->value = kzalloc(new_prop->length, GFP_KERNEL); +	if (!new_prop->value) { +		kfree(new_prop->name); +		kfree(new_prop); +		return -ENOMEM; +	} +	memcpy(new_prop->value, value, vd); + +	val = (u32 *)new_prop->value; +	rc = cxl_update_properties(dn, new_prop); +	pr_devel("%s: update property (%s, length: %i, value: %#x)\n", +		  dn->name, name, vd, be32_to_cpu(*val)); + +	if (rc) { +		kfree(new_prop->name); +		kfree(new_prop->value); +		kfree(new_prop); +	} +	return rc; +} + +static int update_node(__be32 phandle, s32 scope) +{ +	struct update_props_workarea *upwa; +	struct device_node *dn; +	int i, rc, ret; +	char *prop_data; +	char *buf; +	int token; +	u32 nprops; +	u32 vd; + +	token = rtas_token("ibm,update-properties"); +	if (token == RTAS_UNKNOWN_SERVICE) +		return -EINVAL; + +	buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	dn = of_find_node_by_phandle(be32_to_cpu(phandle)); +	if (!dn) { +		kfree(buf); +		return -ENOENT; +	} + +	upwa = (struct update_props_workarea *)&buf[0]; +	upwa->phandle = phandle; +	do { +		rc = rcall(token, buf, scope); +		if (rc < 0) +			break; + +		prop_data = buf + sizeof(*upwa); +		nprops = be32_to_cpu(upwa->nprops); + +		if (*prop_data == 0) { +			prop_data++; +			vd = be32_to_cpu(*(__be32 *)prop_data); +			prop_data += vd + sizeof(vd); +			nprops--; +		} + +		for (i = 0; i < nprops; i++) { +			char *prop_name; + +			prop_name = prop_data; +			prop_data += strlen(prop_name) + 1; +			vd = be32_to_cpu(*(__be32 *)prop_data); +			prop_data += sizeof(vd); + +			if ((vd != 0x00000000) && (vd != 0x80000000)) { +				ret = update_property(dn, prop_name, vd, +						prop_data); +				if (ret) +					pr_err("cxl: Could not update property %s - %i\n", +					       prop_name, ret); + +				prop_data += vd; +			} +		} +	} while (rc == 1); + +	of_node_put(dn); +	kfree(buf); +	return rc; +} + +static int update_devicetree(struct cxl *adapter, s32 scope) +{ +	struct update_nodes_workarea *unwa; +	u32 action, node_count; +	int token, rc, i; +	__be32 *data, drc_index, phandle; +	char *buf; + +	token = rtas_token("ibm,update-nodes"); +	if (token == RTAS_UNKNOWN_SERVICE) +		return -EINVAL; + +	buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	unwa = (struct update_nodes_workarea *)&buf[0]; +	unwa->unit_address = cpu_to_be64(adapter->guest->handle); +	do { +		rc = rcall(token, buf, scope); +		if (rc && rc != 1) +			break; + +		data = (__be32 *)buf + 4; +		while (be32_to_cpu(*data) & NODE_ACTION_MASK) { +			action = be32_to_cpu(*data) & NODE_ACTION_MASK; +			node_count = be32_to_cpu(*data) & NODE_COUNT_MASK; +			pr_devel("device reconfiguration - action: %#x, nodes: %#x\n", +				 action, node_count); +			data++; + +			for (i = 0; i < node_count; i++) { +				phandle = *data++; + +				switch (action) { +				case OPCODE_DELETE: +					/* nothing to do */ +					break; +				case OPCODE_UPDATE: +					update_node(phandle, scope); +					break; +				case OPCODE_ADD: +					/* nothing to do, just move pointer */ +					drc_index = *data++; +					break; +				} +			} +		} +	} while (rc == 1); + +	kfree(buf); +	return 0; +} + +static int handle_image(struct cxl *adapter, int operation, +			long (*fct)(u64, u64, u64, u64 *), +			struct cxl_adapter_image *ai) +{ +	size_t mod, s_copy, len_chunk = 0; +	struct ai_header *header = NULL; +	unsigned int entries = 0, i; +	void *dest, *from; +	int rc = 0, need_header; + +	/* base adapter image header */ +	need_header = (ai->flags & CXL_AI_NEED_HEADER); +	if (need_header) { +		header = kzalloc(sizeof(struct ai_header), GFP_KERNEL); +		if (!header) +			return -ENOMEM; +		header->version = cpu_to_be16(1); +		header->vendor = cpu_to_be16(adapter->guest->vendor); +		header->device = cpu_to_be16(adapter->guest->device); +		header->subsystem_vendor = cpu_to_be16(adapter->guest->subsystem_vendor); +		header->subsystem = cpu_to_be16(adapter->guest->subsystem); +		header->image_offset = cpu_to_be64(CXL_AI_HEADER_SIZE); +		header->image_length = cpu_to_be64(ai->len_image); +	} + +	/* number of entries in the list */ +	len_chunk = ai->len_data; +	if (need_header) +		len_chunk += CXL_AI_HEADER_SIZE; + +	entries = len_chunk / CXL_AI_BUFFER_SIZE; +	mod = len_chunk % CXL_AI_BUFFER_SIZE; +	if (mod) +		entries++; + +	if (entries > CXL_AI_MAX_ENTRIES) { +		rc = -EINVAL; +		goto err; +	} + +	/*          < -- MAX_CHUNK_SIZE = 4096 * 256 = 1048576 bytes --> +	 * chunk 0  ---------------------------------------------------- +	 *          | header   |  data                                 | +	 *          ---------------------------------------------------- +	 * chunk 1  ---------------------------------------------------- +	 *          | data                                             | +	 *          ---------------------------------------------------- +	 * .... +	 * chunk n  ---------------------------------------------------- +	 *          | data                                             | +	 *          ---------------------------------------------------- +	 */ +	from = (void *) ai->data; +	for (i = 0; i < entries; i++) { +		dest = buffer[i]; +		s_copy = CXL_AI_BUFFER_SIZE; + +		if ((need_header) && (i == 0)) { +			/* add adapter image header */ +			memcpy(buffer[i], header, sizeof(struct ai_header)); +			s_copy = CXL_AI_BUFFER_SIZE - CXL_AI_HEADER_SIZE; +			dest += CXL_AI_HEADER_SIZE; /* image offset */ +		} +		if ((i == (entries - 1)) && mod) +			s_copy = mod; + +		/* copy data */ +		if (copy_from_user(dest, from, s_copy)) +			goto err; + +		/* fill in the list */ +		le[i].phys_addr = cpu_to_be64(virt_to_phys(buffer[i])); +		le[i].len = cpu_to_be64(CXL_AI_BUFFER_SIZE); +		if ((i == (entries - 1)) && mod) +			le[i].len = cpu_to_be64(mod); +		from += s_copy; +	} +	pr_devel("%s (op: %i, need header: %i, entries: %i, token: %#llx)\n", +		 __func__, operation, need_header, entries, continue_token); + +	/* +	 * download/validate the adapter image to the coherent +	 * platform facility +	 */ +	rc = fct(adapter->guest->handle, virt_to_phys(le), entries, +		&continue_token); +	if (rc == 0) /* success of download/validation operation */ +		continue_token = 0; + +err: +	kfree(header); + +	return rc; +} + +static int transfer_image(struct cxl *adapter, int operation, +			struct cxl_adapter_image *ai) +{ +	int rc = 0; +	int afu; + +	switch (operation) { +	case DOWNLOAD_IMAGE: +		rc = handle_image(adapter, operation, +				&cxl_h_download_adapter_image, ai); +		if (rc < 0) { +			pr_devel("resetting adapter\n"); +			cxl_h_reset_adapter(adapter->guest->handle); +		} +		return rc; + +	case VALIDATE_IMAGE: +		rc = handle_image(adapter, operation, +				&cxl_h_validate_adapter_image, ai); +		if (rc < 0) { +			pr_devel("resetting adapter\n"); +			cxl_h_reset_adapter(adapter->guest->handle); +			return rc; +		} +		if (rc == 0) { +			pr_devel("remove curent afu\n"); +			for (afu = 0; afu < adapter->slices; afu++) +				cxl_guest_remove_afu(adapter->afu[afu]); + +			pr_devel("resetting adapter\n"); +			cxl_h_reset_adapter(adapter->guest->handle); + +			/* The entire image has now been +			 * downloaded and the validation has +			 * been successfully performed. +			 * After that, the partition should call +			 * ibm,update-nodes and +			 * ibm,update-properties to receive the +			 * current configuration +			 */ +			rc = update_devicetree(adapter, DEVICE_SCOPE); +			transfer = 1; +		} +		return rc; +	} + +	return -EINVAL; +} + +static long ioctl_transfer_image(struct cxl *adapter, int operation, +				struct cxl_adapter_image __user *uai) +{ +	struct cxl_adapter_image ai; + +	pr_devel("%s\n", __func__); + +	if (copy_from_user(&ai, uai, sizeof(struct cxl_adapter_image))) +		return -EFAULT; + +	/* +	 * Make sure reserved fields and bits are set to 0 +	 */ +	if (ai.reserved1 || ai.reserved2 || ai.reserved3 || ai.reserved4 || +		(ai.flags & ~CXL_AI_ALL)) +		return -EINVAL; + +	return transfer_image(adapter, operation, &ai); +} + +static int device_open(struct inode *inode, struct file *file) +{ +	int adapter_num = CXL_DEVT_ADAPTER(inode->i_rdev); +	struct cxl *adapter; +	int rc = 0, i; + +	pr_devel("in %s\n", __func__); + +	BUG_ON(sizeof(struct ai_header) != CXL_AI_HEADER_SIZE); + +	/* Allows one process to open the device by using a semaphore */ +	if (down_interruptible(&sem) != 0) +		return -EPERM; + +	if (!(adapter = get_cxl_adapter(adapter_num))) +		return -ENODEV; + +	file->private_data = adapter; +	continue_token = 0; +	transfer = 0; + +	for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) +		buffer[i] = NULL; + +	/* aligned buffer containing list entries which describes up to +	 * 1 megabyte of data (256 entries of 4096 bytes each) +	 *  Logical real address of buffer 0  -  Buffer 0 length in bytes +	 *  Logical real address of buffer 1  -  Buffer 1 length in bytes +	 *  Logical real address of buffer 2  -  Buffer 2 length in bytes +	 *  .... +	 *  .... +	 *  Logical real address of buffer N  -  Buffer N length in bytes +	 */ +	le = (struct sg_list *)get_zeroed_page(GFP_KERNEL); +	if (!le) { +		rc = -ENOMEM; +		goto err; +	} + +	for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) { +		buffer[i] = (unsigned long *)get_zeroed_page(GFP_KERNEL); +		if (!buffer[i]) { +			rc = -ENOMEM; +			goto err1; +		} +	} + +	return 0; + +err1: +	for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) { +		if (buffer[i]) +			free_page((unsigned long) buffer[i]); +	} + +	if (le) +		free_page((unsigned long) le); +err: +	put_device(&adapter->dev); + +	return rc; +} + +static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ +	struct cxl *adapter = file->private_data; + +	pr_devel("in %s\n", __func__); + +	if (cmd == CXL_IOCTL_DOWNLOAD_IMAGE) +		return ioctl_transfer_image(adapter, +					DOWNLOAD_IMAGE, +					(struct cxl_adapter_image __user *)arg); +	else if (cmd == CXL_IOCTL_VALIDATE_IMAGE) +		return ioctl_transfer_image(adapter, +					VALIDATE_IMAGE, +					(struct cxl_adapter_image __user *)arg); +	else +		return -EINVAL; +} + +static long device_compat_ioctl(struct file *file, unsigned int cmd, +				unsigned long arg) +{ +	return device_ioctl(file, cmd, arg); +} + +static int device_close(struct inode *inode, struct file *file) +{ +	struct cxl *adapter = file->private_data; +	int i; + +	pr_devel("in %s\n", __func__); + +	for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) { +		if (buffer[i]) +			free_page((unsigned long) buffer[i]); +	} + +	if (le) +		free_page((unsigned long) le); + +	up(&sem); +	put_device(&adapter->dev); +	continue_token = 0; + +	/* reload the module */ +	if (transfer) +		cxl_guest_reload_module(adapter); +	else { +		pr_devel("resetting adapter\n"); +		cxl_h_reset_adapter(adapter->guest->handle); +	} + +	transfer = 0; +	return 0; +} + +static const struct file_operations fops = { +	.owner		= THIS_MODULE, +	.open		= device_open, +	.unlocked_ioctl	= device_ioctl, +	.compat_ioctl	= device_compat_ioctl, +	.release	= device_close, +}; + +void cxl_guest_remove_chardev(struct cxl *adapter) +{ +	cdev_del(&adapter->guest->cdev); +} + +int cxl_guest_add_chardev(struct cxl *adapter) +{ +	dev_t devt; +	int rc; + +	devt = MKDEV(MAJOR(cxl_get_dev()), CXL_CARD_MINOR(adapter)); +	cdev_init(&adapter->guest->cdev, &fops); +	if ((rc = cdev_add(&adapter->guest->cdev, devt, 1))) { +		dev_err(&adapter->dev, +			"Unable to add chardev on adapter (card%i): %i\n", +			adapter->adapter_num, rc); +		goto err; +	} +	adapter->dev.devt = devt; +	sema_init(&sem, 1); +err: +	return rc; +}  | 
