diff options
Diffstat (limited to 'arch/powerpc/platforms/powernv/opal-flash.c')
| -rw-r--r-- | arch/powerpc/platforms/powernv/opal-flash.c | 588 | 
1 files changed, 588 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/powernv/opal-flash.c b/arch/powerpc/platforms/powernv/opal-flash.c new file mode 100644 index 00000000000..5c21d9c07f4 --- /dev/null +++ b/arch/powerpc/platforms/powernv/opal-flash.c @@ -0,0 +1,588 @@ +/* + * PowerNV OPAL Firmware Update Interface + * + * Copyright 2013 IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#define DEBUG + +#include <linux/kernel.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/kobject.h> +#include <linux/sysfs.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/pagemap.h> +#include <linux/delay.h> + +#include <asm/opal.h> + +/* FLASH status codes */ +#define FLASH_NO_OP		-1099	/* No operation initiated by user */ +#define FLASH_NO_AUTH		-9002	/* Not a service authority partition */ + +/* Validate image status values */ +#define VALIDATE_IMG_READY	-1001	/* Image ready for validation */ +#define VALIDATE_IMG_INCOMPLETE	-1002	/* User copied < VALIDATE_BUF_SIZE */ + +/* Manage image status values */ +#define MANAGE_ACTIVE_ERR	-9001	/* Cannot overwrite active img */ + +/* Flash image status values */ +#define FLASH_IMG_READY		0	/* Img ready for flash on reboot */ +#define FLASH_INVALID_IMG	-1003	/* Flash image shorter than expected */ +#define FLASH_IMG_NULL_DATA	-1004	/* Bad data in sg list entry */ +#define FLASH_IMG_BAD_LEN	-1005	/* Bad length in sg list entry */ + +/* Manage operation tokens */ +#define FLASH_REJECT_TMP_SIDE	0	/* Reject temporary fw image */ +#define FLASH_COMMIT_TMP_SIDE	1	/* Commit temporary fw image */ + +/* Update tokens */ +#define FLASH_UPDATE_CANCEL	0	/* Cancel update request */ +#define FLASH_UPDATE_INIT	1	/* Initiate update */ + +/* Validate image update result tokens */ +#define VALIDATE_TMP_UPDATE	0     /* T side will be updated */ +#define VALIDATE_FLASH_AUTH	1     /* Partition does not have authority */ +#define VALIDATE_INVALID_IMG	2     /* Candidate image is not valid */ +#define VALIDATE_CUR_UNKNOWN	3     /* Current fixpack level is unknown */ +/* + * Current T side will be committed to P side before being replace with new + * image, and the new image is downlevel from current image + */ +#define VALIDATE_TMP_COMMIT_DL	4 +/* + * Current T side will be committed to P side before being replaced with new + * image + */ +#define VALIDATE_TMP_COMMIT	5 +/* + * T side will be updated with a downlevel image + */ +#define VALIDATE_TMP_UPDATE_DL	6 +/* + * The candidate image's release date is later than the system's firmware + * service entitlement date - service warranty period has expired + */ +#define VALIDATE_OUT_OF_WRNTY	7 + +/* Validate buffer size */ +#define VALIDATE_BUF_SIZE	4096 + +/* XXX: Assume candidate image size is <= 1GB */ +#define MAX_IMAGE_SIZE	0x40000000 + +/* Image status */ +enum { +	IMAGE_INVALID, +	IMAGE_LOADING, +	IMAGE_READY, +}; + +/* Candidate image data */ +struct image_data_t { +	int		status; +	void		*data; +	uint32_t	size; +}; + +/* Candidate image header */ +struct image_header_t { +	uint16_t	magic; +	uint16_t	version; +	uint32_t	size; +}; + +struct validate_flash_t { +	int		status;		/* Return status */ +	void		*buf;		/* Candidate image buffer */ +	uint32_t	buf_size;	/* Image size */ +	uint32_t	result;		/* Update results token */ +}; + +struct manage_flash_t { +	int status;		/* Return status */ +}; + +struct update_flash_t { +	int status;		/* Return status */ +}; + +static struct image_header_t	image_header; +static struct image_data_t	image_data; +static struct validate_flash_t	validate_flash_data; +static struct manage_flash_t	manage_flash_data; +static struct update_flash_t	update_flash_data; + +static DEFINE_MUTEX(image_data_mutex); + +/* + * Validate candidate image + */ +static inline void opal_flash_validate(void) +{ +	long ret; +	void *buf = validate_flash_data.buf; +	__be32 size = cpu_to_be32(validate_flash_data.buf_size); +	__be32 result; + +	ret = opal_validate_flash(__pa(buf), &size, &result); + +	validate_flash_data.status = ret; +	validate_flash_data.buf_size = be32_to_cpu(size); +	validate_flash_data.result = be32_to_cpu(result); +} + +/* + * Validate output format: + *     validate result token + *     current image version details + *     new image version details + */ +static ssize_t validate_show(struct kobject *kobj, +			     struct kobj_attribute *attr, char *buf) +{ +	struct validate_flash_t *args_buf = &validate_flash_data; +	int len; + +	/* Candidate image is not validated */ +	if (args_buf->status < VALIDATE_TMP_UPDATE) { +		len = sprintf(buf, "%d\n", args_buf->status); +		goto out; +	} + +	/* Result token */ +	len = sprintf(buf, "%d\n", args_buf->result); + +	/* Current and candidate image version details */ +	if ((args_buf->result != VALIDATE_TMP_UPDATE) && +	    (args_buf->result < VALIDATE_CUR_UNKNOWN)) +		goto out; + +	if (args_buf->buf_size > (VALIDATE_BUF_SIZE - len)) { +		memcpy(buf + len, args_buf->buf, VALIDATE_BUF_SIZE - len); +		len = VALIDATE_BUF_SIZE; +	} else { +		memcpy(buf + len, args_buf->buf, args_buf->buf_size); +		len += args_buf->buf_size; +	} +out: +	/* Set status to default */ +	args_buf->status = FLASH_NO_OP; +	return len; +} + +/* + * Validate candidate firmware image + * + * Note: + *   We are only interested in first 4K bytes of the + *   candidate image. + */ +static ssize_t validate_store(struct kobject *kobj, +			      struct kobj_attribute *attr, +			      const char *buf, size_t count) +{ +	struct validate_flash_t *args_buf = &validate_flash_data; + +	if (buf[0] != '1') +		return -EINVAL; + +	mutex_lock(&image_data_mutex); + +	if (image_data.status != IMAGE_READY || +	    image_data.size < VALIDATE_BUF_SIZE) { +		args_buf->result = VALIDATE_INVALID_IMG; +		args_buf->status = VALIDATE_IMG_INCOMPLETE; +		goto out; +	} + +	/* Copy first 4k bytes of candidate image */ +	memcpy(args_buf->buf, image_data.data, VALIDATE_BUF_SIZE); + +	args_buf->status = VALIDATE_IMG_READY; +	args_buf->buf_size = VALIDATE_BUF_SIZE; + +	/* Validate candidate image */ +	opal_flash_validate(); + +out: +	mutex_unlock(&image_data_mutex); +	return count; +} + +/* + * Manage flash routine + */ +static inline void opal_flash_manage(uint8_t op) +{ +	struct manage_flash_t *const args_buf = &manage_flash_data; + +	args_buf->status = opal_manage_flash(op); +} + +/* + * Show manage flash status + */ +static ssize_t manage_show(struct kobject *kobj, +			   struct kobj_attribute *attr, char *buf) +{ +	struct manage_flash_t *const args_buf = &manage_flash_data; +	int rc; + +	rc = sprintf(buf, "%d\n", args_buf->status); +	/* Set status to default*/ +	args_buf->status = FLASH_NO_OP; +	return rc; +} + +/* + * Manage operations: + *   0 - Reject + *   1 - Commit + */ +static ssize_t manage_store(struct kobject *kobj, +			    struct kobj_attribute *attr, +			    const char *buf, size_t count) +{ +	uint8_t op; +	switch (buf[0]) { +	case '0': +		op = FLASH_REJECT_TMP_SIDE; +		break; +	case '1': +		op = FLASH_COMMIT_TMP_SIDE; +		break; +	default: +		return -EINVAL; +	} + +	/* commit/reject temporary image */ +	opal_flash_manage(op); +	return count; +} + +/* + * OPAL update flash + */ +static int opal_flash_update(int op) +{ +	struct opal_sg_list *list; +	unsigned long addr; +	int64_t rc = OPAL_PARAMETER; + +	if (op == FLASH_UPDATE_CANCEL) { +		pr_alert("FLASH: Image update cancelled\n"); +		addr = '\0'; +		goto flash; +	} + +	list = opal_vmalloc_to_sg_list(image_data.data, image_data.size); +	if (!list) +		goto invalid_img; + +	/* First entry address */ +	addr = __pa(list); + +flash: +	rc = opal_update_flash(addr); + +invalid_img: +	return rc; +} + +/* Return CPUs to OPAL before starting FW update */ +static void flash_return_cpu(void *info) +{ +	int cpu = smp_processor_id(); + +	if (!cpu_online(cpu)) +		return; + +	/* Disable IRQ */ +	hard_irq_disable(); + +	/* Return the CPU to OPAL */ +	opal_return_cpu(); +} + +/* This gets called just before system reboots */ +void opal_flash_term_callback(void) +{ +	struct cpumask mask; + +	if (update_flash_data.status != FLASH_IMG_READY) +		return; + +	pr_alert("FLASH: Flashing new firmware\n"); +	pr_alert("FLASH: Image is %u bytes\n", image_data.size); +	pr_alert("FLASH: Performing flash and reboot/shutdown\n"); +	pr_alert("FLASH: This will take several minutes. Do not power off!\n"); + +	/* Small delay to help getting the above message out */ +	msleep(500); + +	/* Return secondary CPUs to firmware */ +	cpumask_copy(&mask, cpu_online_mask); +	cpumask_clear_cpu(smp_processor_id(), &mask); +	if (!cpumask_empty(&mask)) +		smp_call_function_many(&mask, +				       flash_return_cpu, NULL, false); +	/* Hard disable interrupts */ +	hard_irq_disable(); +} + +/* + * Show candidate image status + */ +static ssize_t update_show(struct kobject *kobj, +			   struct kobj_attribute *attr, char *buf) +{ +	struct update_flash_t *const args_buf = &update_flash_data; +	return sprintf(buf, "%d\n", args_buf->status); +} + +/* + * Set update image flag + *  1 - Flash new image + *  0 - Cancel flash request + */ +static ssize_t update_store(struct kobject *kobj, +			    struct kobj_attribute *attr, +			    const char *buf, size_t count) +{ +	struct update_flash_t *const args_buf = &update_flash_data; +	int rc = count; + +	mutex_lock(&image_data_mutex); + +	switch (buf[0]) { +	case '0': +		if (args_buf->status == FLASH_IMG_READY) +			opal_flash_update(FLASH_UPDATE_CANCEL); +		args_buf->status = FLASH_NO_OP; +		break; +	case '1': +		/* Image is loaded? */ +		if (image_data.status == IMAGE_READY) +			args_buf->status = +				opal_flash_update(FLASH_UPDATE_INIT); +		else +			args_buf->status = FLASH_INVALID_IMG; +		break; +	default: +		rc = -EINVAL; +	} + +	mutex_unlock(&image_data_mutex); +	return rc; +} + +/* + * Free image buffer + */ +static void free_image_buf(void) +{ +	void *addr; +	int size; + +	addr = image_data.data; +	size = PAGE_ALIGN(image_data.size); +	while (size > 0) { +		ClearPageReserved(vmalloc_to_page(addr)); +		addr += PAGE_SIZE; +		size -= PAGE_SIZE; +	} +	vfree(image_data.data); +	image_data.data = NULL; +	image_data.status = IMAGE_INVALID; +} + +/* + * Allocate image buffer. + */ +static int alloc_image_buf(char *buffer, size_t count) +{ +	void *addr; +	int size; + +	if (count < sizeof(struct image_header_t)) { +		pr_warn("FLASH: Invalid candidate image\n"); +		return -EINVAL; +	} + +	memcpy(&image_header, (void *)buffer, sizeof(struct image_header_t)); +	image_data.size = be32_to_cpu(image_header.size); +	pr_debug("FLASH: Candidate image size = %u\n", image_data.size); + +	if (image_data.size > MAX_IMAGE_SIZE) { +		pr_warn("FLASH: Too large image\n"); +		return -EINVAL; +	} +	if (image_data.size < VALIDATE_BUF_SIZE) { +		pr_warn("FLASH: Image is shorter than expected\n"); +		return -EINVAL; +	} + +	image_data.data = vzalloc(PAGE_ALIGN(image_data.size)); +	if (!image_data.data) { +		pr_err("%s : Failed to allocate memory\n", __func__); +		return -ENOMEM; +	} + +	/* Pin memory */ +	addr = image_data.data; +	size = PAGE_ALIGN(image_data.size); +	while (size > 0) { +		SetPageReserved(vmalloc_to_page(addr)); +		addr += PAGE_SIZE; +		size -= PAGE_SIZE; +	} + +	image_data.status = IMAGE_LOADING; +	return 0; +} + +/* + * Copy candidate image + * + * Parse candidate image header to get total image size + * and pre-allocate required memory. + */ +static ssize_t image_data_write(struct file *filp, struct kobject *kobj, +				struct bin_attribute *bin_attr, +				char *buffer, loff_t pos, size_t count) +{ +	int rc; + +	mutex_lock(&image_data_mutex); + +	/* New image ? */ +	if (pos == 0) { +		/* Free memory, if already allocated */ +		if (image_data.data) +			free_image_buf(); + +		/* Cancel outstanding image update request */ +		if (update_flash_data.status == FLASH_IMG_READY) +			opal_flash_update(FLASH_UPDATE_CANCEL); + +		/* Allocate memory */ +		rc = alloc_image_buf(buffer, count); +		if (rc) +			goto out; +	} + +	if (image_data.status != IMAGE_LOADING) { +		rc = -ENOMEM; +		goto out; +	} + +	if ((pos + count) > image_data.size) { +		rc = -EINVAL; +		goto out; +	} + +	memcpy(image_data.data + pos, (void *)buffer, count); +	rc = count; + +	/* Set image status */ +	if ((pos + count) == image_data.size) { +		pr_debug("FLASH: Candidate image loaded....\n"); +		image_data.status = IMAGE_READY; +	} + +out: +	mutex_unlock(&image_data_mutex); +	return rc; +} + +/* + * sysfs interface : + *  OPAL uses below sysfs files for code update. + *  We create these files under /sys/firmware/opal. + * + *   image		: Interface to load candidate firmware image + *   validate_flash	: Validate firmware image + *   manage_flash	: Commit/Reject firmware image + *   update_flash	: Flash new firmware image + * + */ +static struct bin_attribute image_data_attr = { +	.attr = {.name = "image", .mode = 0200}, +	.size = MAX_IMAGE_SIZE,	/* Limit image size */ +	.write = image_data_write, +}; + +static struct kobj_attribute validate_attribute = +	__ATTR(validate_flash, 0600, validate_show, validate_store); + +static struct kobj_attribute manage_attribute = +	__ATTR(manage_flash, 0600, manage_show, manage_store); + +static struct kobj_attribute update_attribute = +	__ATTR(update_flash, 0600, update_show, update_store); + +static struct attribute *image_op_attrs[] = { +	&validate_attribute.attr, +	&manage_attribute.attr, +	&update_attribute.attr, +	NULL	/* need to NULL terminate the list of attributes */ +}; + +static struct attribute_group image_op_attr_group = { +	.attrs = image_op_attrs, +}; + +void __init opal_flash_init(void) +{ +	int ret; + +	/* Allocate validate image buffer */ +	validate_flash_data.buf = kzalloc(VALIDATE_BUF_SIZE, GFP_KERNEL); +	if (!validate_flash_data.buf) { +		pr_err("%s : Failed to allocate memory\n", __func__); +		return; +	} + +	/* Make sure /sys/firmware/opal directory is created */ +	if (!opal_kobj) { +		pr_warn("FLASH: opal kobject is not available\n"); +		goto nokobj; +	} + +	/* Create the sysfs files */ +	ret = sysfs_create_group(opal_kobj, &image_op_attr_group); +	if (ret) { +		pr_warn("FLASH: Failed to create sysfs files\n"); +		goto nokobj; +	} + +	ret = sysfs_create_bin_file(opal_kobj, &image_data_attr); +	if (ret) { +		pr_warn("FLASH: Failed to create sysfs files\n"); +		goto nosysfs_file; +	} + +	/* Set default status */ +	validate_flash_data.status = FLASH_NO_OP; +	manage_flash_data.status = FLASH_NO_OP; +	update_flash_data.status = FLASH_NO_OP; +	image_data.status = IMAGE_INVALID; +	return; + +nosysfs_file: +	sysfs_remove_group(opal_kobj, &image_op_attr_group); + +nokobj: +	kfree(validate_flash_data.buf); +	return; +}  | 
