|
@@ -0,0 +1,667 @@
|
|
|
+/*
|
|
|
+ * 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 <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 <= 256MB */
|
|
|
+#define MAX_IMAGE_SIZE 0x10000000
|
|
|
+
|
|
|
+/* Flash sg list version */
|
|
|
+#define SG_LIST_VERSION (1UL)
|
|
|
+
|
|
|
+/* 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;
|
|
|
+};
|
|
|
+
|
|
|
+/* Scatter/gather entry */
|
|
|
+struct opal_sg_entry {
|
|
|
+ void *data;
|
|
|
+ long length;
|
|
|
+};
|
|
|
+
|
|
|
+/* We calculate number of entries based on PAGE_SIZE */
|
|
|
+#define SG_ENTRIES_PER_NODE ((PAGE_SIZE - 16) / sizeof(struct opal_sg_entry))
|
|
|
+
|
|
|
+/*
|
|
|
+ * This struct is very similar but not identical to that
|
|
|
+ * needed by the opal flash update. All we need to do for
|
|
|
+ * opal is rewrite num_entries into a version/length and
|
|
|
+ * translate the pointers to absolute.
|
|
|
+ */
|
|
|
+struct opal_sg_list {
|
|
|
+ unsigned long num_entries;
|
|
|
+ struct opal_sg_list *next;
|
|
|
+ struct opal_sg_entry entry[SG_ENTRIES_PER_NODE];
|
|
|
+};
|
|
|
+
|
|
|
+struct validate_flash_t {
|
|
|
+ int status; /* Return status */
|
|
|
+ void *buf; /* Candiate 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)
|
|
|
+{
|
|
|
+ struct validate_flash_t *args_buf = &validate_flash_data;
|
|
|
+
|
|
|
+ args_buf->status = opal_validate_flash(__pa(args_buf->buf),
|
|
|
+ &(args_buf->buf_size),
|
|
|
+ &(args_buf->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;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Free sg list
|
|
|
+ */
|
|
|
+static void free_sg_list(struct opal_sg_list *list)
|
|
|
+{
|
|
|
+ struct opal_sg_list *sg1;
|
|
|
+ while (list) {
|
|
|
+ sg1 = list->next;
|
|
|
+ kfree(list);
|
|
|
+ list = sg1;
|
|
|
+ }
|
|
|
+ list = NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Build candidate image scatter gather list
|
|
|
+ *
|
|
|
+ * list format:
|
|
|
+ * -----------------------------------
|
|
|
+ * | VER (8) | Entry length in bytes |
|
|
|
+ * -----------------------------------
|
|
|
+ * | Pointer to next entry |
|
|
|
+ * -----------------------------------
|
|
|
+ * | Address of memory area 1 |
|
|
|
+ * -----------------------------------
|
|
|
+ * | Length of memory area 1 |
|
|
|
+ * -----------------------------------
|
|
|
+ * | ......... |
|
|
|
+ * -----------------------------------
|
|
|
+ * | ......... |
|
|
|
+ * -----------------------------------
|
|
|
+ * | Address of memory area N |
|
|
|
+ * -----------------------------------
|
|
|
+ * | Length of memory area N |
|
|
|
+ * -----------------------------------
|
|
|
+ */
|
|
|
+static struct opal_sg_list *image_data_to_sglist(void)
|
|
|
+{
|
|
|
+ struct opal_sg_list *sg1, *list = NULL;
|
|
|
+ void *addr;
|
|
|
+ int size;
|
|
|
+
|
|
|
+ addr = image_data.data;
|
|
|
+ size = image_data.size;
|
|
|
+
|
|
|
+ sg1 = kzalloc((sizeof(struct opal_sg_list)), GFP_KERNEL);
|
|
|
+ if (!sg1)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ list = sg1;
|
|
|
+ sg1->num_entries = 0;
|
|
|
+ while (size > 0) {
|
|
|
+ /* Translate virtual address to physical address */
|
|
|
+ sg1->entry[sg1->num_entries].data =
|
|
|
+ (void *)(vmalloc_to_pfn(addr) << PAGE_SHIFT);
|
|
|
+
|
|
|
+ if (size > PAGE_SIZE)
|
|
|
+ sg1->entry[sg1->num_entries].length = PAGE_SIZE;
|
|
|
+ else
|
|
|
+ sg1->entry[sg1->num_entries].length = size;
|
|
|
+
|
|
|
+ sg1->num_entries++;
|
|
|
+ if (sg1->num_entries >= SG_ENTRIES_PER_NODE) {
|
|
|
+ sg1->next = kzalloc((sizeof(struct opal_sg_list)),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!sg1->next) {
|
|
|
+ pr_err("%s : Failed to allocate memory\n",
|
|
|
+ __func__);
|
|
|
+ goto nomem;
|
|
|
+ }
|
|
|
+
|
|
|
+ sg1 = sg1->next;
|
|
|
+ sg1->num_entries = 0;
|
|
|
+ }
|
|
|
+ addr += PAGE_SIZE;
|
|
|
+ size -= PAGE_SIZE;
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+nomem:
|
|
|
+ free_sg_list(list);
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * OPAL update flash
|
|
|
+ */
|
|
|
+static int opal_flash_update(int op)
|
|
|
+{
|
|
|
+ struct opal_sg_list *sg, *list, *next;
|
|
|
+ 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 = image_data_to_sglist();
|
|
|
+ if (!list)
|
|
|
+ goto invalid_img;
|
|
|
+
|
|
|
+ /* First entry address */
|
|
|
+ addr = __pa(list);
|
|
|
+
|
|
|
+ /* Translate sg list address to absolute */
|
|
|
+ for (sg = list; sg; sg = next) {
|
|
|
+ next = sg->next;
|
|
|
+ /* Don't translate NULL pointer for last entry */
|
|
|
+ if (sg->next)
|
|
|
+ sg->next = (struct opal_sg_list *)__pa(sg->next);
|
|
|
+ else
|
|
|
+ sg->next = NULL;
|
|
|
+
|
|
|
+ /* Make num_entries into the version/length field */
|
|
|
+ sg->num_entries = (SG_LIST_VERSION << 56) |
|
|
|
+ (sg->num_entries * sizeof(struct opal_sg_entry) + 16);
|
|
|
+ }
|
|
|
+
|
|
|
+ pr_alert("FLASH: Image is %u bytes\n", image_data.size);
|
|
|
+ pr_alert("FLASH: Image update requested\n");
|
|
|
+ pr_alert("FLASH: Image will be updated during system reboot\n");
|
|
|
+ pr_alert("FLASH: This will take several minutes. Do not power off!\n");
|
|
|
+
|
|
|
+flash:
|
|
|
+ rc = opal_update_flash(addr);
|
|
|
+
|
|
|
+invalid_img:
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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: Candiate 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;
|
|
|
+}
|