mirror of
				https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
				synced 2025-10-29 23:28:52 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			588 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			588 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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;
 | |
| }
 | 
