cregit-Linux how code gets into the kernel

Release 4.14 arch/powerpc/platforms/powernv/opal-flash.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;

/* Initialize update_flash_data status to No Operation */

static struct update_flash_t	update_flash_data = {
	.status = FLASH_NO_OP,
};

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); }

Contributors

PersonTokensPropCommitsCommitProp
Anton Blanchard3750.68%133.33%
Vasant Hegde3649.32%266.67%
Total73100.00%3100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde163100.00%1100.00%
Total163100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde123100.00%1100.00%
Total123100.00%1100.00%

/* * 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); }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde28100.00%1100.00%
Total28100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde55100.00%1100.00%
Total55100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde66100.00%1100.00%
Total66100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde7688.37%266.67%
Anton Blanchard1011.63%133.33%
Total86100.00%3100.00%

/* 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(); }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde33100.00%1100.00%
Total33100.00%1100.00%

/* 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(); }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde93100.00%2100.00%
Total93100.00%2100.00%

/* * 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); }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde41100.00%1100.00%
Total41100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde120100.00%1100.00%
Total120100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde73100.00%1100.00%
Total73100.00%1100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde19999.50%150.00%
Masanari Iida10.50%150.00%
Total200100.00%2100.00%

/* * 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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde188100.00%1100.00%
Total188100.00%1100.00%

/* * 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 const 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_update_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; }

Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde15099.34%150.00%
Cyril Bur10.66%150.00%
Total151100.00%2100.00%


Overall Contributors

PersonTokensPropCommitsCommitProp
Vasant Hegde185797.33%550.00%
Anton Blanchard472.46%220.00%
Masanari Iida20.10%110.00%
Cyril Bur10.05%110.00%
Bhumika Goyal10.05%110.00%
Total1908100.00%10100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.