forked from Minki/linux
ed59190e41
This change adds the OPAL interface definitions to allow Linux to read, write and erase from system flash devices. We register platform devices for the flash devices exported by firmware. We clash with the existing opal_flash_init function, which is really for the FSP flash update functionality, so we rename that initcall to opal_flash_update_init(). A future change will add an mtd driver that uses this interface. Changes from Joel Stanley and Jeremy Kerr. Signed-off-by: Cyril Bur <cyrilbur@gmail.com> Signed-off-by: Jeremy Kerr <jk@ozlabs.org> Signed-off-by: Joel Stanley <joel@jms.id.au> Acked-by: Stewart Smith <stewart@linux.vnet.ibm.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
593 lines
14 KiB
C
593 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;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/*
|
|
* 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_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;
|
|
}
|