162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * PowerNV OPAL Firmware Update Interface
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2013 IBM Corp.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define DEBUG
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/reboot.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/kobject.h>
1462306a36Sopenharmony_ci#include <linux/sysfs.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/mm.h>
1762306a36Sopenharmony_ci#include <linux/vmalloc.h>
1862306a36Sopenharmony_ci#include <linux/pagemap.h>
1962306a36Sopenharmony_ci#include <linux/delay.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <asm/opal.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* FLASH status codes */
2462306a36Sopenharmony_ci#define FLASH_NO_OP		-1099	/* No operation initiated by user */
2562306a36Sopenharmony_ci#define FLASH_NO_AUTH		-9002	/* Not a service authority partition */
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* Validate image status values */
2862306a36Sopenharmony_ci#define VALIDATE_IMG_READY	-1001	/* Image ready for validation */
2962306a36Sopenharmony_ci#define VALIDATE_IMG_INCOMPLETE	-1002	/* User copied < VALIDATE_BUF_SIZE */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* Manage image status values */
3262306a36Sopenharmony_ci#define MANAGE_ACTIVE_ERR	-9001	/* Cannot overwrite active img */
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/* Flash image status values */
3562306a36Sopenharmony_ci#define FLASH_IMG_READY		0	/* Img ready for flash on reboot */
3662306a36Sopenharmony_ci#define FLASH_INVALID_IMG	-1003	/* Flash image shorter than expected */
3762306a36Sopenharmony_ci#define FLASH_IMG_NULL_DATA	-1004	/* Bad data in sg list entry */
3862306a36Sopenharmony_ci#define FLASH_IMG_BAD_LEN	-1005	/* Bad length in sg list entry */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* Manage operation tokens */
4162306a36Sopenharmony_ci#define FLASH_REJECT_TMP_SIDE	0	/* Reject temporary fw image */
4262306a36Sopenharmony_ci#define FLASH_COMMIT_TMP_SIDE	1	/* Commit temporary fw image */
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* Update tokens */
4562306a36Sopenharmony_ci#define FLASH_UPDATE_CANCEL	0	/* Cancel update request */
4662306a36Sopenharmony_ci#define FLASH_UPDATE_INIT	1	/* Initiate update */
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* Validate image update result tokens */
4962306a36Sopenharmony_ci#define VALIDATE_TMP_UPDATE	0     /* T side will be updated */
5062306a36Sopenharmony_ci#define VALIDATE_FLASH_AUTH	1     /* Partition does not have authority */
5162306a36Sopenharmony_ci#define VALIDATE_INVALID_IMG	2     /* Candidate image is not valid */
5262306a36Sopenharmony_ci#define VALIDATE_CUR_UNKNOWN	3     /* Current fixpack level is unknown */
5362306a36Sopenharmony_ci/*
5462306a36Sopenharmony_ci * Current T side will be committed to P side before being replace with new
5562306a36Sopenharmony_ci * image, and the new image is downlevel from current image
5662306a36Sopenharmony_ci */
5762306a36Sopenharmony_ci#define VALIDATE_TMP_COMMIT_DL	4
5862306a36Sopenharmony_ci/*
5962306a36Sopenharmony_ci * Current T side will be committed to P side before being replaced with new
6062306a36Sopenharmony_ci * image
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_ci#define VALIDATE_TMP_COMMIT	5
6362306a36Sopenharmony_ci/*
6462306a36Sopenharmony_ci * T side will be updated with a downlevel image
6562306a36Sopenharmony_ci */
6662306a36Sopenharmony_ci#define VALIDATE_TMP_UPDATE_DL	6
6762306a36Sopenharmony_ci/*
6862306a36Sopenharmony_ci * The candidate image's release date is later than the system's firmware
6962306a36Sopenharmony_ci * service entitlement date - service warranty period has expired
7062306a36Sopenharmony_ci */
7162306a36Sopenharmony_ci#define VALIDATE_OUT_OF_WRNTY	7
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/* Validate buffer size */
7462306a36Sopenharmony_ci#define VALIDATE_BUF_SIZE	4096
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/* XXX: Assume candidate image size is <= 1GB */
7762306a36Sopenharmony_ci#define MAX_IMAGE_SIZE	0x40000000
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/* Image status */
8062306a36Sopenharmony_cienum {
8162306a36Sopenharmony_ci	IMAGE_INVALID,
8262306a36Sopenharmony_ci	IMAGE_LOADING,
8362306a36Sopenharmony_ci	IMAGE_READY,
8462306a36Sopenharmony_ci};
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci/* Candidate image data */
8762306a36Sopenharmony_cistruct image_data_t {
8862306a36Sopenharmony_ci	int		status;
8962306a36Sopenharmony_ci	void		*data;
9062306a36Sopenharmony_ci	uint32_t	size;
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci/* Candidate image header */
9462306a36Sopenharmony_cistruct image_header_t {
9562306a36Sopenharmony_ci	uint16_t	magic;
9662306a36Sopenharmony_ci	uint16_t	version;
9762306a36Sopenharmony_ci	uint32_t	size;
9862306a36Sopenharmony_ci};
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistruct validate_flash_t {
10162306a36Sopenharmony_ci	int		status;		/* Return status */
10262306a36Sopenharmony_ci	void		*buf;		/* Candidate image buffer */
10362306a36Sopenharmony_ci	uint32_t	buf_size;	/* Image size */
10462306a36Sopenharmony_ci	uint32_t	result;		/* Update results token */
10562306a36Sopenharmony_ci};
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistruct manage_flash_t {
10862306a36Sopenharmony_ci	int status;		/* Return status */
10962306a36Sopenharmony_ci};
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistruct update_flash_t {
11262306a36Sopenharmony_ci	int status;		/* Return status */
11362306a36Sopenharmony_ci};
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic struct image_header_t	image_header;
11662306a36Sopenharmony_cistatic struct image_data_t	image_data;
11762306a36Sopenharmony_cistatic struct validate_flash_t	validate_flash_data;
11862306a36Sopenharmony_cistatic struct manage_flash_t	manage_flash_data;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/* Initialize update_flash_data status to No Operation */
12162306a36Sopenharmony_cistatic struct update_flash_t	update_flash_data = {
12262306a36Sopenharmony_ci	.status = FLASH_NO_OP,
12362306a36Sopenharmony_ci};
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic DEFINE_MUTEX(image_data_mutex);
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci/*
12862306a36Sopenharmony_ci * Validate candidate image
12962306a36Sopenharmony_ci */
13062306a36Sopenharmony_cistatic inline void opal_flash_validate(void)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	long ret;
13362306a36Sopenharmony_ci	void *buf = validate_flash_data.buf;
13462306a36Sopenharmony_ci	__be32 size = cpu_to_be32(validate_flash_data.buf_size);
13562306a36Sopenharmony_ci	__be32 result;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	ret = opal_validate_flash(__pa(buf), &size, &result);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	validate_flash_data.status = ret;
14062306a36Sopenharmony_ci	validate_flash_data.buf_size = be32_to_cpu(size);
14162306a36Sopenharmony_ci	validate_flash_data.result = be32_to_cpu(result);
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci/*
14562306a36Sopenharmony_ci * Validate output format:
14662306a36Sopenharmony_ci *     validate result token
14762306a36Sopenharmony_ci *     current image version details
14862306a36Sopenharmony_ci *     new image version details
14962306a36Sopenharmony_ci */
15062306a36Sopenharmony_cistatic ssize_t validate_show(struct kobject *kobj,
15162306a36Sopenharmony_ci			     struct kobj_attribute *attr, char *buf)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct validate_flash_t *args_buf = &validate_flash_data;
15462306a36Sopenharmony_ci	int len;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Candidate image is not validated */
15762306a36Sopenharmony_ci	if (args_buf->status < VALIDATE_TMP_UPDATE) {
15862306a36Sopenharmony_ci		len = sprintf(buf, "%d\n", args_buf->status);
15962306a36Sopenharmony_ci		goto out;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Result token */
16362306a36Sopenharmony_ci	len = sprintf(buf, "%d\n", args_buf->result);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* Current and candidate image version details */
16662306a36Sopenharmony_ci	if ((args_buf->result != VALIDATE_TMP_UPDATE) &&
16762306a36Sopenharmony_ci	    (args_buf->result < VALIDATE_CUR_UNKNOWN))
16862306a36Sopenharmony_ci		goto out;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	if (args_buf->buf_size > (VALIDATE_BUF_SIZE - len)) {
17162306a36Sopenharmony_ci		memcpy(buf + len, args_buf->buf, VALIDATE_BUF_SIZE - len);
17262306a36Sopenharmony_ci		len = VALIDATE_BUF_SIZE;
17362306a36Sopenharmony_ci	} else {
17462306a36Sopenharmony_ci		memcpy(buf + len, args_buf->buf, args_buf->buf_size);
17562306a36Sopenharmony_ci		len += args_buf->buf_size;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ciout:
17862306a36Sopenharmony_ci	/* Set status to default */
17962306a36Sopenharmony_ci	args_buf->status = FLASH_NO_OP;
18062306a36Sopenharmony_ci	return len;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci/*
18462306a36Sopenharmony_ci * Validate candidate firmware image
18562306a36Sopenharmony_ci *
18662306a36Sopenharmony_ci * Note:
18762306a36Sopenharmony_ci *   We are only interested in first 4K bytes of the
18862306a36Sopenharmony_ci *   candidate image.
18962306a36Sopenharmony_ci */
19062306a36Sopenharmony_cistatic ssize_t validate_store(struct kobject *kobj,
19162306a36Sopenharmony_ci			      struct kobj_attribute *attr,
19262306a36Sopenharmony_ci			      const char *buf, size_t count)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct validate_flash_t *args_buf = &validate_flash_data;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (buf[0] != '1')
19762306a36Sopenharmony_ci		return -EINVAL;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	mutex_lock(&image_data_mutex);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	if (image_data.status != IMAGE_READY ||
20262306a36Sopenharmony_ci	    image_data.size < VALIDATE_BUF_SIZE) {
20362306a36Sopenharmony_ci		args_buf->result = VALIDATE_INVALID_IMG;
20462306a36Sopenharmony_ci		args_buf->status = VALIDATE_IMG_INCOMPLETE;
20562306a36Sopenharmony_ci		goto out;
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	/* Copy first 4k bytes of candidate image */
20962306a36Sopenharmony_ci	memcpy(args_buf->buf, image_data.data, VALIDATE_BUF_SIZE);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	args_buf->status = VALIDATE_IMG_READY;
21262306a36Sopenharmony_ci	args_buf->buf_size = VALIDATE_BUF_SIZE;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* Validate candidate image */
21562306a36Sopenharmony_ci	opal_flash_validate();
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ciout:
21862306a36Sopenharmony_ci	mutex_unlock(&image_data_mutex);
21962306a36Sopenharmony_ci	return count;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci/*
22362306a36Sopenharmony_ci * Manage flash routine
22462306a36Sopenharmony_ci */
22562306a36Sopenharmony_cistatic inline void opal_flash_manage(uint8_t op)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	struct manage_flash_t *const args_buf = &manage_flash_data;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	args_buf->status = opal_manage_flash(op);
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci/*
23362306a36Sopenharmony_ci * Show manage flash status
23462306a36Sopenharmony_ci */
23562306a36Sopenharmony_cistatic ssize_t manage_show(struct kobject *kobj,
23662306a36Sopenharmony_ci			   struct kobj_attribute *attr, char *buf)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	struct manage_flash_t *const args_buf = &manage_flash_data;
23962306a36Sopenharmony_ci	int rc;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	rc = sprintf(buf, "%d\n", args_buf->status);
24262306a36Sopenharmony_ci	/* Set status to default*/
24362306a36Sopenharmony_ci	args_buf->status = FLASH_NO_OP;
24462306a36Sopenharmony_ci	return rc;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci/*
24862306a36Sopenharmony_ci * Manage operations:
24962306a36Sopenharmony_ci *   0 - Reject
25062306a36Sopenharmony_ci *   1 - Commit
25162306a36Sopenharmony_ci */
25262306a36Sopenharmony_cistatic ssize_t manage_store(struct kobject *kobj,
25362306a36Sopenharmony_ci			    struct kobj_attribute *attr,
25462306a36Sopenharmony_ci			    const char *buf, size_t count)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	uint8_t op;
25762306a36Sopenharmony_ci	switch (buf[0]) {
25862306a36Sopenharmony_ci	case '0':
25962306a36Sopenharmony_ci		op = FLASH_REJECT_TMP_SIDE;
26062306a36Sopenharmony_ci		break;
26162306a36Sopenharmony_ci	case '1':
26262306a36Sopenharmony_ci		op = FLASH_COMMIT_TMP_SIDE;
26362306a36Sopenharmony_ci		break;
26462306a36Sopenharmony_ci	default:
26562306a36Sopenharmony_ci		return -EINVAL;
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	/* commit/reject temporary image */
26962306a36Sopenharmony_ci	opal_flash_manage(op);
27062306a36Sopenharmony_ci	return count;
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci/*
27462306a36Sopenharmony_ci * OPAL update flash
27562306a36Sopenharmony_ci */
27662306a36Sopenharmony_cistatic int opal_flash_update(int op)
27762306a36Sopenharmony_ci{
27862306a36Sopenharmony_ci	struct opal_sg_list *list;
27962306a36Sopenharmony_ci	unsigned long addr;
28062306a36Sopenharmony_ci	int64_t rc = OPAL_PARAMETER;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	if (op == FLASH_UPDATE_CANCEL) {
28362306a36Sopenharmony_ci		pr_alert("FLASH: Image update cancelled\n");
28462306a36Sopenharmony_ci		addr = '\0';
28562306a36Sopenharmony_ci		goto flash;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	list = opal_vmalloc_to_sg_list(image_data.data, image_data.size);
28962306a36Sopenharmony_ci	if (!list)
29062306a36Sopenharmony_ci		goto invalid_img;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/* First entry address */
29362306a36Sopenharmony_ci	addr = __pa(list);
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ciflash:
29662306a36Sopenharmony_ci	rc = opal_update_flash(addr);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ciinvalid_img:
29962306a36Sopenharmony_ci	return rc;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci/* This gets called just before system reboots */
30362306a36Sopenharmony_civoid opal_flash_update_print_message(void)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	if (update_flash_data.status != FLASH_IMG_READY)
30662306a36Sopenharmony_ci		return;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	pr_alert("FLASH: Flashing new firmware\n");
30962306a36Sopenharmony_ci	pr_alert("FLASH: Image is %u bytes\n", image_data.size);
31062306a36Sopenharmony_ci	pr_alert("FLASH: Performing flash and reboot/shutdown\n");
31162306a36Sopenharmony_ci	pr_alert("FLASH: This will take several minutes. Do not power off!\n");
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	/* Small delay to help getting the above message out */
31462306a36Sopenharmony_ci	msleep(500);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci/*
31862306a36Sopenharmony_ci * Show candidate image status
31962306a36Sopenharmony_ci */
32062306a36Sopenharmony_cistatic ssize_t update_show(struct kobject *kobj,
32162306a36Sopenharmony_ci			   struct kobj_attribute *attr, char *buf)
32262306a36Sopenharmony_ci{
32362306a36Sopenharmony_ci	struct update_flash_t *const args_buf = &update_flash_data;
32462306a36Sopenharmony_ci	return sprintf(buf, "%d\n", args_buf->status);
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci/*
32862306a36Sopenharmony_ci * Set update image flag
32962306a36Sopenharmony_ci *  1 - Flash new image
33062306a36Sopenharmony_ci *  0 - Cancel flash request
33162306a36Sopenharmony_ci */
33262306a36Sopenharmony_cistatic ssize_t update_store(struct kobject *kobj,
33362306a36Sopenharmony_ci			    struct kobj_attribute *attr,
33462306a36Sopenharmony_ci			    const char *buf, size_t count)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	struct update_flash_t *const args_buf = &update_flash_data;
33762306a36Sopenharmony_ci	int rc = count;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	mutex_lock(&image_data_mutex);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	switch (buf[0]) {
34262306a36Sopenharmony_ci	case '0':
34362306a36Sopenharmony_ci		if (args_buf->status == FLASH_IMG_READY)
34462306a36Sopenharmony_ci			opal_flash_update(FLASH_UPDATE_CANCEL);
34562306a36Sopenharmony_ci		args_buf->status = FLASH_NO_OP;
34662306a36Sopenharmony_ci		break;
34762306a36Sopenharmony_ci	case '1':
34862306a36Sopenharmony_ci		/* Image is loaded? */
34962306a36Sopenharmony_ci		if (image_data.status == IMAGE_READY)
35062306a36Sopenharmony_ci			args_buf->status =
35162306a36Sopenharmony_ci				opal_flash_update(FLASH_UPDATE_INIT);
35262306a36Sopenharmony_ci		else
35362306a36Sopenharmony_ci			args_buf->status = FLASH_INVALID_IMG;
35462306a36Sopenharmony_ci		break;
35562306a36Sopenharmony_ci	default:
35662306a36Sopenharmony_ci		rc = -EINVAL;
35762306a36Sopenharmony_ci	}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	mutex_unlock(&image_data_mutex);
36062306a36Sopenharmony_ci	return rc;
36162306a36Sopenharmony_ci}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci/*
36462306a36Sopenharmony_ci * Free image buffer
36562306a36Sopenharmony_ci */
36662306a36Sopenharmony_cistatic void free_image_buf(void)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	void *addr;
36962306a36Sopenharmony_ci	int size;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	addr = image_data.data;
37262306a36Sopenharmony_ci	size = PAGE_ALIGN(image_data.size);
37362306a36Sopenharmony_ci	while (size > 0) {
37462306a36Sopenharmony_ci		ClearPageReserved(vmalloc_to_page(addr));
37562306a36Sopenharmony_ci		addr += PAGE_SIZE;
37662306a36Sopenharmony_ci		size -= PAGE_SIZE;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci	vfree(image_data.data);
37962306a36Sopenharmony_ci	image_data.data = NULL;
38062306a36Sopenharmony_ci	image_data.status = IMAGE_INVALID;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci/*
38462306a36Sopenharmony_ci * Allocate image buffer.
38562306a36Sopenharmony_ci */
38662306a36Sopenharmony_cistatic int alloc_image_buf(char *buffer, size_t count)
38762306a36Sopenharmony_ci{
38862306a36Sopenharmony_ci	void *addr;
38962306a36Sopenharmony_ci	int size;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	if (count < sizeof(image_header)) {
39262306a36Sopenharmony_ci		pr_warn("FLASH: Invalid candidate image\n");
39362306a36Sopenharmony_ci		return -EINVAL;
39462306a36Sopenharmony_ci	}
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	memcpy(&image_header, (void *)buffer, sizeof(image_header));
39762306a36Sopenharmony_ci	image_data.size = be32_to_cpu(image_header.size);
39862306a36Sopenharmony_ci	pr_debug("FLASH: Candidate image size = %u\n", image_data.size);
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	if (image_data.size > MAX_IMAGE_SIZE) {
40162306a36Sopenharmony_ci		pr_warn("FLASH: Too large image\n");
40262306a36Sopenharmony_ci		return -EINVAL;
40362306a36Sopenharmony_ci	}
40462306a36Sopenharmony_ci	if (image_data.size < VALIDATE_BUF_SIZE) {
40562306a36Sopenharmony_ci		pr_warn("FLASH: Image is shorter than expected\n");
40662306a36Sopenharmony_ci		return -EINVAL;
40762306a36Sopenharmony_ci	}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	image_data.data = vzalloc(PAGE_ALIGN(image_data.size));
41062306a36Sopenharmony_ci	if (!image_data.data) {
41162306a36Sopenharmony_ci		pr_err("%s : Failed to allocate memory\n", __func__);
41262306a36Sopenharmony_ci		return -ENOMEM;
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	/* Pin memory */
41662306a36Sopenharmony_ci	addr = image_data.data;
41762306a36Sopenharmony_ci	size = PAGE_ALIGN(image_data.size);
41862306a36Sopenharmony_ci	while (size > 0) {
41962306a36Sopenharmony_ci		SetPageReserved(vmalloc_to_page(addr));
42062306a36Sopenharmony_ci		addr += PAGE_SIZE;
42162306a36Sopenharmony_ci		size -= PAGE_SIZE;
42262306a36Sopenharmony_ci	}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	image_data.status = IMAGE_LOADING;
42562306a36Sopenharmony_ci	return 0;
42662306a36Sopenharmony_ci}
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci/*
42962306a36Sopenharmony_ci * Copy candidate image
43062306a36Sopenharmony_ci *
43162306a36Sopenharmony_ci * Parse candidate image header to get total image size
43262306a36Sopenharmony_ci * and pre-allocate required memory.
43362306a36Sopenharmony_ci */
43462306a36Sopenharmony_cistatic ssize_t image_data_write(struct file *filp, struct kobject *kobj,
43562306a36Sopenharmony_ci				struct bin_attribute *bin_attr,
43662306a36Sopenharmony_ci				char *buffer, loff_t pos, size_t count)
43762306a36Sopenharmony_ci{
43862306a36Sopenharmony_ci	int rc;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	mutex_lock(&image_data_mutex);
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	/* New image ? */
44362306a36Sopenharmony_ci	if (pos == 0) {
44462306a36Sopenharmony_ci		/* Free memory, if already allocated */
44562306a36Sopenharmony_ci		if (image_data.data)
44662306a36Sopenharmony_ci			free_image_buf();
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci		/* Cancel outstanding image update request */
44962306a36Sopenharmony_ci		if (update_flash_data.status == FLASH_IMG_READY)
45062306a36Sopenharmony_ci			opal_flash_update(FLASH_UPDATE_CANCEL);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci		/* Allocate memory */
45362306a36Sopenharmony_ci		rc = alloc_image_buf(buffer, count);
45462306a36Sopenharmony_ci		if (rc)
45562306a36Sopenharmony_ci			goto out;
45662306a36Sopenharmony_ci	}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	if (image_data.status != IMAGE_LOADING) {
45962306a36Sopenharmony_ci		rc = -ENOMEM;
46062306a36Sopenharmony_ci		goto out;
46162306a36Sopenharmony_ci	}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	if ((pos + count) > image_data.size) {
46462306a36Sopenharmony_ci		rc = -EINVAL;
46562306a36Sopenharmony_ci		goto out;
46662306a36Sopenharmony_ci	}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	memcpy(image_data.data + pos, (void *)buffer, count);
46962306a36Sopenharmony_ci	rc = count;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	/* Set image status */
47262306a36Sopenharmony_ci	if ((pos + count) == image_data.size) {
47362306a36Sopenharmony_ci		pr_debug("FLASH: Candidate image loaded....\n");
47462306a36Sopenharmony_ci		image_data.status = IMAGE_READY;
47562306a36Sopenharmony_ci	}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ciout:
47862306a36Sopenharmony_ci	mutex_unlock(&image_data_mutex);
47962306a36Sopenharmony_ci	return rc;
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci/*
48362306a36Sopenharmony_ci * sysfs interface :
48462306a36Sopenharmony_ci *  OPAL uses below sysfs files for code update.
48562306a36Sopenharmony_ci *  We create these files under /sys/firmware/opal.
48662306a36Sopenharmony_ci *
48762306a36Sopenharmony_ci *   image		: Interface to load candidate firmware image
48862306a36Sopenharmony_ci *   validate_flash	: Validate firmware image
48962306a36Sopenharmony_ci *   manage_flash	: Commit/Reject firmware image
49062306a36Sopenharmony_ci *   update_flash	: Flash new firmware image
49162306a36Sopenharmony_ci *
49262306a36Sopenharmony_ci */
49362306a36Sopenharmony_cistatic const struct bin_attribute image_data_attr = {
49462306a36Sopenharmony_ci	.attr = {.name = "image", .mode = 0200},
49562306a36Sopenharmony_ci	.size = MAX_IMAGE_SIZE,	/* Limit image size */
49662306a36Sopenharmony_ci	.write = image_data_write,
49762306a36Sopenharmony_ci};
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_cistatic struct kobj_attribute validate_attribute =
50062306a36Sopenharmony_ci	__ATTR(validate_flash, 0600, validate_show, validate_store);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_cistatic struct kobj_attribute manage_attribute =
50362306a36Sopenharmony_ci	__ATTR(manage_flash, 0600, manage_show, manage_store);
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_cistatic struct kobj_attribute update_attribute =
50662306a36Sopenharmony_ci	__ATTR(update_flash, 0600, update_show, update_store);
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic struct attribute *image_op_attrs[] = {
50962306a36Sopenharmony_ci	&validate_attribute.attr,
51062306a36Sopenharmony_ci	&manage_attribute.attr,
51162306a36Sopenharmony_ci	&update_attribute.attr,
51262306a36Sopenharmony_ci	NULL	/* need to NULL terminate the list of attributes */
51362306a36Sopenharmony_ci};
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_cistatic const struct attribute_group image_op_attr_group = {
51662306a36Sopenharmony_ci	.attrs = image_op_attrs,
51762306a36Sopenharmony_ci};
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_civoid __init opal_flash_update_init(void)
52062306a36Sopenharmony_ci{
52162306a36Sopenharmony_ci	int ret;
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	/* Firmware update is not supported by firmware */
52462306a36Sopenharmony_ci	if (!opal_check_token(OPAL_FLASH_VALIDATE))
52562306a36Sopenharmony_ci		return;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	/* Allocate validate image buffer */
52862306a36Sopenharmony_ci	validate_flash_data.buf = kzalloc(VALIDATE_BUF_SIZE, GFP_KERNEL);
52962306a36Sopenharmony_ci	if (!validate_flash_data.buf) {
53062306a36Sopenharmony_ci		pr_err("%s : Failed to allocate memory\n", __func__);
53162306a36Sopenharmony_ci		return;
53262306a36Sopenharmony_ci	}
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	/* Make sure /sys/firmware/opal directory is created */
53562306a36Sopenharmony_ci	if (!opal_kobj) {
53662306a36Sopenharmony_ci		pr_warn("FLASH: opal kobject is not available\n");
53762306a36Sopenharmony_ci		goto nokobj;
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	/* Create the sysfs files */
54162306a36Sopenharmony_ci	ret = sysfs_create_group(opal_kobj, &image_op_attr_group);
54262306a36Sopenharmony_ci	if (ret) {
54362306a36Sopenharmony_ci		pr_warn("FLASH: Failed to create sysfs files\n");
54462306a36Sopenharmony_ci		goto nokobj;
54562306a36Sopenharmony_ci	}
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	ret = sysfs_create_bin_file(opal_kobj, &image_data_attr);
54862306a36Sopenharmony_ci	if (ret) {
54962306a36Sopenharmony_ci		pr_warn("FLASH: Failed to create sysfs files\n");
55062306a36Sopenharmony_ci		goto nosysfs_file;
55162306a36Sopenharmony_ci	}
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	/* Set default status */
55462306a36Sopenharmony_ci	validate_flash_data.status = FLASH_NO_OP;
55562306a36Sopenharmony_ci	manage_flash_data.status = FLASH_NO_OP;
55662306a36Sopenharmony_ci	update_flash_data.status = FLASH_NO_OP;
55762306a36Sopenharmony_ci	image_data.status = IMAGE_INVALID;
55862306a36Sopenharmony_ci	return;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_cinosysfs_file:
56162306a36Sopenharmony_ci	sysfs_remove_group(opal_kobj, &image_op_attr_group);
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_cinokobj:
56462306a36Sopenharmony_ci	kfree(validate_flash_data.buf);
56562306a36Sopenharmony_ci	return;
56662306a36Sopenharmony_ci}
567