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